From 4bb37729ec3e43e27ee8555bc8650f218b633dcf Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Wed, 15 Jul 2026 15:44:28 -0600 Subject: [PATCH 01/81] =?UTF-8?q?=F0=9F=90=9B=20Fix=20Swift=20local-first?= =?UTF-8?q?=20loading=20states?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/PackRat/Features/Packs/PacksListView.swift | 3 +-- .../PackRat/Features/Packs/PacksViewModel.swift | 2 +- .../Sources/PackRat/Features/Trips/TripsListView.swift | 3 +-- .../PackRat/Features/Trips/TripsViewModel.swift | 2 +- apps/swift/Sources/PackRat/PackRatApp.swift | 10 ++++------ .../PackRat/Persistence/PersistenceController.swift | 4 ++++ 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/swift/Sources/PackRat/Features/Packs/PacksListView.swift b/apps/swift/Sources/PackRat/Features/Packs/PacksListView.swift index f7a52f6c98..2d13dfa26f 100644 --- a/apps/swift/Sources/PackRat/Features/Packs/PacksListView.swift +++ b/apps/swift/Sources/PackRat/Features/Packs/PacksListView.swift @@ -27,7 +27,7 @@ struct PacksListView: View { var body: some View { Group { - if viewModel.isLoading && viewModel.packs.isEmpty && !isExplore { + if viewModel.isLoading && viewModel.packs.isEmpty && !viewModel.isCacheLoaded && !isExplore { ProgressView("Loading packs…").frame(maxWidth: .infinity, maxHeight: .infinity) } else if let error = viewModel.error, viewModel.packs.isEmpty, !isExplore { ErrorView(error, retry: { await viewModel.load(context: modelContext) }) @@ -64,7 +64,6 @@ struct PacksListView: View { Button("New Pack", systemImage: "plus") { showingCreateSheet = true } .accessibilityIdentifier("packs_new_pack_button") .keyboardShortcut("n", modifiers: .command) - .accessibilityIdentifier("new_pack_button") } if viewModel.isLoading || isLoadingPublic { ProgressView().controlSize(.small) diff --git a/apps/swift/Sources/PackRat/Features/Packs/PacksViewModel.swift b/apps/swift/Sources/PackRat/Features/Packs/PacksViewModel.swift index 404de863a6..feedbb65b8 100644 --- a/apps/swift/Sources/PackRat/Features/Packs/PacksViewModel.swift +++ b/apps/swift/Sources/PackRat/Features/Packs/PacksViewModel.swift @@ -54,8 +54,8 @@ final class PacksViewModel { let cachedPacks = cached.compactMap { $0.toPack() } if !cachedPacks.isEmpty { packs = cachedPacks - isCacheLoaded = true } + isCacheLoaded = true } isLoading = packs.isEmpty diff --git a/apps/swift/Sources/PackRat/Features/Trips/TripsListView.swift b/apps/swift/Sources/PackRat/Features/Trips/TripsListView.swift index ae73fe78d7..8cc9593c87 100644 --- a/apps/swift/Sources/PackRat/Features/Trips/TripsListView.swift +++ b/apps/swift/Sources/PackRat/Features/Trips/TripsListView.swift @@ -16,7 +16,7 @@ struct TripsListView: View { var body: some View { Group { - if viewModel.isLoading && viewModel.trips.isEmpty { + if viewModel.isLoading && viewModel.trips.isEmpty && !viewModel.isCacheLoaded { ProgressView("Loading trips…").frame(maxWidth: .infinity, maxHeight: .infinity) } else if let error = viewModel.error, viewModel.trips.isEmpty { ErrorView(error, retry: { await viewModel.load(context: modelContext) }) @@ -42,7 +42,6 @@ struct TripsListView: View { Button("Plan Trip", systemImage: "plus") { showingCreateSheet = true } .accessibilityIdentifier("trips_plan_trip_button") .keyboardShortcut("n", modifiers: [.command, .shift]) - .accessibilityIdentifier("plan_trip_button") } } .task { await viewModel.load(context: modelContext) } diff --git a/apps/swift/Sources/PackRat/Features/Trips/TripsViewModel.swift b/apps/swift/Sources/PackRat/Features/Trips/TripsViewModel.swift index f3fb8290e2..20edd2ed26 100644 --- a/apps/swift/Sources/PackRat/Features/Trips/TripsViewModel.swift +++ b/apps/swift/Sources/PackRat/Features/Trips/TripsViewModel.swift @@ -66,8 +66,8 @@ final class TripsViewModel { let cachedTrips = cached.compactMap { $0.toTrip() } if !cachedTrips.isEmpty { trips = cachedTrips - isCacheLoaded = true } + isCacheLoaded = true } isLoading = trips.isEmpty diff --git a/apps/swift/Sources/PackRat/PackRatApp.swift b/apps/swift/Sources/PackRat/PackRatApp.swift index 138a89eb5c..06c61fffc1 100644 --- a/apps/swift/Sources/PackRat/PackRatApp.swift +++ b/apps/swift/Sources/PackRat/PackRatApp.swift @@ -12,6 +12,10 @@ struct PackRatApp: App { #endif init() { + // Telemetry has to start before any view is mounted so launch-time + // errors are captured. A missing DSN silently disables the SDK. + SentryConfig.start() + #if os(macOS) if ProcessInfo.processInfo.arguments.contains("--reset-auth") { UserDefaults.standard.set(true, forKey: "ApplePersistenceIgnoreState") @@ -20,12 +24,6 @@ struct PackRatApp: App { #endif } - init() { - // Telemetry has to start before any view is mounted so launch-time - // errors are captured. A missing DSN silently disables the SDK. - SentryConfig.start() - } - var body: some Scene { WindowGroup { AuthGateView() diff --git a/apps/swift/Sources/PackRat/Persistence/PersistenceController.swift b/apps/swift/Sources/PackRat/Persistence/PersistenceController.swift index 9edc7f3c11..d8181c7eef 100644 --- a/apps/swift/Sources/PackRat/Persistence/PersistenceController.swift +++ b/apps/swift/Sources/PackRat/Persistence/PersistenceController.swift @@ -11,6 +11,10 @@ final class PersistenceController { let schema = Schema([CachedPack.self, CachedTrip.self, ShoppingItem.self]) let config = ModelConfiguration("PackRat", schema: schema) do { + try FileManager.default.createDirectory( + at: FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0], + withIntermediateDirectories: true + ) container = try ModelContainer(for: schema, configurations: config) } catch { fatalError("SwiftData container failed: \(error)") From e1d6d07e22f3a5c5bb8add48e437eb643987c49a Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Wed, 15 Jul 2026 15:45:19 -0600 Subject: [PATCH 02/81] =?UTF-8?q?=F0=9F=A7=AA=20Harden=20Swift=20E2E=20scr?= =?UTF-8?q?eenshot=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Tests/PackRatUITests/HomeTileTests.swift | 27 +- .../PackRatUITests/ScreenshotSmokeTests.swift | 12 +- .../VisualScreenshotTests.swift | 74 +++-- apps/swift/scripts/__tests__/args.test.ts | 14 + .../scripts/capture-visual-screenshots.ts | 260 ++++++++++++++++-- apps/swift/scripts/lib/args.ts | 17 ++ package.json | 10 +- 7 files changed, 363 insertions(+), 51 deletions(-) diff --git a/apps/swift/Tests/PackRatUITests/HomeTileTests.swift b/apps/swift/Tests/PackRatUITests/HomeTileTests.swift index abeddb2662..bd8540a256 100644 --- a/apps/swift/Tests/PackRatUITests/HomeTileTests.swift +++ b/apps/swift/Tests/PackRatUITests/HomeTileTests.swift @@ -22,19 +22,19 @@ final class HomeTileTests: AppUITestCase { func testEveryHomeNavigationTileOpensDestination() { for tile in navigationTiles { - goToTab("Home") + goHome() tapHomeTile(tile.id) guard let destinationTitle = tile.destinationTitle else { continue } XCTAssertTrue( - app.navigationBars[destinationTitle].waitForExistence(timeout: 8), + destinationExists(destinationTitle, timeout: 8), "\(tile.id) must open \(destinationTitle)" ) } } func testSeasonSuggestionsTileOpensAndDismissesSheet() { - goToTab("Home") + goHome() tapHomeTile("home_tile_season_suggestions") XCTAssertTrue( @@ -46,7 +46,7 @@ final class HomeTileTests: AppUITestCase { } func testShoppingListTileSupportsAddToggleClearAndDone() { - goToTab("Home") + goHome() tapHomeTile("home_tile_shopping_list") XCTAssertTrue( @@ -86,7 +86,24 @@ final class HomeTileTests: AppUITestCase { ) waitFor(app.buttons["shopping_done"], timeout: 5).tap() - XCTAssertTrue(app.navigationBars["Home"].waitForExistence(timeout: 5)) + XCTAssertTrue(destinationExists("Home", timeout: 5)) + } + + private func goHome() { + #if os(macOS) + goToSidebar("Home") + #else + goToTab("Home") + #endif + } + + private func destinationExists(_ title: String, timeout: TimeInterval) -> Bool { + #if os(macOS) + return app.staticTexts[title].waitForExistence(timeout: timeout) + || app.navigationBars[title].waitForExistence(timeout: 1) + #else + return app.navigationBars[title].waitForExistence(timeout: timeout) + #endif } private func tapHomeTile(_ id: String) { diff --git a/apps/swift/Tests/PackRatUITests/ScreenshotSmokeTests.swift b/apps/swift/Tests/PackRatUITests/ScreenshotSmokeTests.swift index f199282f31..a2979e6fab 100644 --- a/apps/swift/Tests/PackRatUITests/ScreenshotSmokeTests.swift +++ b/apps/swift/Tests/PackRatUITests/ScreenshotSmokeTests.swift @@ -9,11 +9,11 @@ final class ScreenshotSmokeTests: AppUITestCase { func testCaptureCoreScreens() throws { capture("02-home") - goToTab("Packs") + goToDestination("Packs") XCTAssertTrue(app.navigationBars["Packs"].waitForExistence(timeout: 8)) capture("03-packs") - goToTab("Weather") + goToDestination("Weather") XCTAssertTrue(app.navigationBars["Weather"].waitForExistence(timeout: 8)) let searchField = app.textFields["Search locations..."].exists ? app.textFields["Search locations..."] @@ -40,4 +40,12 @@ final class ScreenshotSmokeTests: AppUITestCase { attachment.lifetime = .keepAlways add(attachment) } + + private func goToDestination(_ label: String) { + #if os(macOS) + goToSidebar(label) + #else + goToTab(label) + #endif + } } diff --git a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift index 4df5fd375e..07e49e52e6 100644 --- a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift +++ b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift @@ -583,10 +583,26 @@ final class VisualScreenshotTests: XCTestCase { openHomeForActionBaseline(name: baselineName) let identifier = "home_action_\(title.lowercased().filter { $0.isLetter || $0.isNumber })" + if prefersHomeSearch(for: title) { + openHomeActionUsingSearch(title: title, identifier: identifier) + if let destinationIdentifier { + let destination = app.descendants(matching: .any).matching(identifier: destinationIdentifier).firstMatch + XCTAssertTrue( + destination.waitForExistence(timeout: 5), + "Expected Home action '\(title)' to open '\(destinationIdentifier)' for screenshot \(name)" + ) + } + capture(name) + if dismissAfterCapture { + dismissPhoneDestination() + } + return + } + let action = app.buttons[identifier] var visibleCandidate: XCUIElement? - for _ in 0..<30 { + for _ in 0..<8 { if action.exists { visibleCandidate = action } @@ -641,6 +657,17 @@ final class VisualScreenshotTests: XCTestCase { } } + private func prefersHomeSearch(for title: String) -> Bool { + [ + "AI Packs", + "Community Feed", + "Gear Inventory", + "Pack Templates", + "Trail Conditions", + "Wildlife ID", + ].contains(title) + } + private func openHomeActionUsingSearch(title: String, identifier: String) { resetActiveHomeSearchPresentation() @@ -1039,6 +1066,7 @@ final class VisualScreenshotTests: XCTestCase { ) { let element = app.descendants(matching: .any).matching(identifier: identifier).firstMatch XCTAssertTrue(element.waitForExistence(timeout: 5), "Expected element identifier '\(identifier)' for screenshot \(name)") + XCTAssertTrue(element.isHittable, "Expected element identifier '\(identifier)' to be hittable for screenshot \(name)") activate(element) capture(name) if dismissAfterCapture { @@ -1048,7 +1076,7 @@ final class VisualScreenshotTests: XCTestCase { private func scrollToElement(identifier: String, maxSwipes: Int = 5) { let element = app.descendants(matching: .any).matching(identifier: identifier).firstMatch - for _ in 0.. XCUIElement? { + let query = app.descendants(matching: .any).matching(identifier: identifier) + return findConcreteElement(in: query, timeout: timeout) + } + private func findConcreteElement(in query: XCUIElementQuery, timeout: TimeInterval) -> XCUIElement? { guard query.firstMatch.waitForExistence(timeout: timeout) else { return nil } for element in query.allElementsBoundByIndex where element.exists && element.isHittable { @@ -1166,6 +1199,14 @@ final class VisualScreenshotTests: XCTestCase { #endif } + private func dismissTransientOverlay() { + #if os(macOS) + app.typeKey(XCUIKeyboardKey.escape.rawValue, modifierFlags: []) + #else + app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.22)).tap() + #endif + } + private func launchAuthenticated(sampleData: Bool = false, forceOffline: Bool = false) { let bundle = Bundle(for: VisualScreenshotTests.self) let email = (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_EMAIL") as? String) @@ -1325,13 +1366,10 @@ final class VisualScreenshotTests: XCTestCase { private func dismissInterruption(in container: XCUIElement) -> Bool { #if os(macOS) for label in ["Remind Me Later", "Not Now", "Continue", "OK", "Allow", "Dismiss", "Close"] { - let button = container.buttons[label] - if button.exists { - if button.isHittable { - button.click() - } else { - button.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).click() - } + let matchingButtons = container.buttons.allElementsBoundByIndex + .filter { $0.label == label || $0.identifier == label } + if let button = matchingButtons.first(where: { $0.exists && $0.isHittable }) ?? matchingButtons.first(where: { $0.exists }) { + button.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).click() return true } } diff --git a/apps/swift/scripts/__tests__/args.test.ts b/apps/swift/scripts/__tests__/args.test.ts index ec586805d8..23cadda01c 100644 --- a/apps/swift/scripts/__tests__/args.test.ts +++ b/apps/swift/scripts/__tests__/args.test.ts @@ -23,6 +23,20 @@ describe('parseArgs', () => { expect(parseArgs(['--plan=smoke']).plan).toBe('iOS-Smoke'); }); + it('accepts legacy positional iOS mode aliases used by package scripts', () => { + expect(parseArgs(['ios-ui'])).toEqual({ plan: 'iOS-Full', passthrough: [] }); + expect(parseArgs(['ios-smoke'])).toEqual({ plan: 'iOS-Smoke', passthrough: [] }); + }); + + it('maps unit mode to the iOS unit test target instead of an xcodebuild action', () => { + expect(parseArgs(['unit'])).toEqual({ + passthrough: ['-only-testing:PackRatTests'], + }); + expect(parseArgs(['ios-unit'])).toEqual({ + passthrough: ['-only-testing:PackRatTests'], + }); + }); + it('case-insensitive alias matching', () => { expect(parseArgs(['--plan', 'SMOKE']).plan).toBe('iOS-Smoke'); expect(parseArgs(['--plan', 'FULL']).plan).toBe('iOS-Full'); diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index 68ac432dc1..a031281f5f 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -4,11 +4,14 @@ import { cpSync, existsSync, mkdirSync, + mkdtempSync, readdirSync, readFileSync, rmSync, + statSync, writeFileSync, } from 'node:fs'; +import { tmpdir } from 'node:os'; import { basename, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { APP_CONFIG } from '@packrat/config/config'; @@ -741,9 +744,26 @@ function loadEnvFile(envFile: string): void { function pickIOSDestination(platform: Extract): string { if (platform === 'ipad') { return pickAvailableIOSDestination({ - preferredNames: ['iPad Pro 13-inch (M5)', 'iPad Pro 11-inch (M5)', 'iPad Air 13-inch (M4)'], + preferredNames: [ + 'PackRat E2E iPad', + 'iPad Pro 13-inch (M5)', + 'iPad Pro 11-inch (M5)', + 'iPad Air 13-inch (M4)', + 'iPad Air 13-inch (M2)', + 'iPad Pro (12.9-inch) (6th generation)', + ], fallbackName: 'iPad Pro 13-inch (M5)', nameIncludes: 'iPad', + createIfMissing: { + name: 'PackRat E2E iPad', + preferredDeviceTypes: [ + 'iPad Pro 13-inch (M5)', + 'iPad Pro 11-inch (M5)', + 'iPad Air 13-inch (M4)', + 'iPad Air 13-inch (M2)', + 'iPad Pro (12.9-inch) (6th generation)', + ], + }, }); } return pickAvailableIOSDestination({ @@ -757,10 +777,12 @@ function pickAvailableIOSDestination({ preferredNames, fallbackName, nameIncludes, + createIfMissing, }: { preferredNames: string[]; fallbackName: string; nameIncludes: string; + createIfMissing?: { name: string; preferredDeviceTypes: string[] }; }): string { const result = spawnSync('xcrun', ['simctl', 'list', 'devices', 'available', '-j'], { encoding: 'utf8', @@ -787,9 +809,91 @@ function pickAvailableIOSDestination({ } } catch {} } + if (createIfMissing) { + const createdDeviceId = createIOSSimulator(createIfMissing); + if (createdDeviceId) return `platform=iOS Simulator,id=${createdDeviceId}`; + } return `platform=iOS Simulator,name=${fallbackName}`; } +function createIOSSimulator({ + name, + preferredDeviceTypes, +}: { + name: string; + preferredDeviceTypes: string[]; +}): string | null { + const deviceTypeId = pickDeviceTypeId(preferredDeviceTypes); + const runtimeId = pickLatestIOSRuntimeId(); + if (!deviceTypeId || !runtimeId) return null; + const result = spawnSync('xcrun', ['simctl', 'create', name, deviceTypeId, runtimeId], { + encoding: 'utf8', + timeout: 30_000, + }); + if (result.status !== 0) return null; + const deviceId = result.stdout.trim(); + return deviceId || null; +} + +function pickDeviceTypeId(preferredNames: string[]): string | null { + const result = spawnSync('xcrun', ['simctl', 'list', 'devicetypes', '-j'], { + encoding: 'utf8', + timeout: 10_000, + maxBuffer: 10 * 1024 * 1024, + }); + if (result.status !== 0) return null; + try { + const parsed = safeJsonParse<{ + devicetypes?: Array<{ name?: string; identifier?: string }>; + }>(result.stdout, { strict: true }); + for (const preferredName of preferredNames) { + const deviceType = parsed.devicetypes?.find((candidate) => candidate.name === preferredName); + if (deviceType?.identifier) return deviceType.identifier; + } + } catch {} + return null; +} + +function pickLatestIOSRuntimeId(): string | null { + const result = spawnSync('xcrun', ['simctl', 'list', 'runtimes', '-j'], { + encoding: 'utf8', + timeout: 10_000, + maxBuffer: 10 * 1024 * 1024, + }); + if (result.status !== 0) return null; + try { + const parsed = safeJsonParse<{ + runtimes?: Array<{ + identifier?: string; + isAvailable?: boolean; + platform?: string; + version?: string; + }>; + }>(result.stdout, { strict: true }); + const runtimes = (parsed.runtimes ?? []) + .filter( + (runtime) => + runtime.isAvailable && + runtime.identifier && + (runtime.platform === 'iOS' || runtime.identifier.includes('iOS')), + ) + .sort((a, b) => compareVersions(b.version ?? '', a.version ?? '')); + return runtimes[0]?.identifier ?? null; + } catch {} + return null; +} + +function compareVersions(left: string, right: string): number { + const leftParts = left.split('.').map(Number); + const rightParts = right.split('.').map(Number); + const count = Math.max(leftParts.length, rightParts.length); + for (let index = 0; index < count; index += 1) { + const diff = (leftParts[index] || 0) - (rightParts[index] || 0); + if (diff !== 0) return diff; + } + return 0; +} + function allocateResultBundle(platform: Platform): string { if (!existsSync(RESULTS_DIR)) mkdirSync(RESULTS_DIR, { recursive: true }); const stamp = new Date().toISOString().replaceAll(':', '-').replaceAll('.', '-'); @@ -843,7 +947,10 @@ function runXcodeVisualTest(platform: Platform, screenshotDir: string): Promise< console.log(`→ XCTest write dir: ${writableScreenshotDir}`); console.log(`→ Result bundle: ${resultBundle}`); - if (platform === 'macos') assertAutomationModeAvailable(); + if (platform === 'macos') { + assertAutomationModeAvailable(); + clearMacNotificationBanners(); + } return new Promise((resolvePromise, reject) => { let timedOut = false; @@ -1490,11 +1597,8 @@ async function screenshotHtml({ outputPath: string; platform: Platform; }): Promise { - const chrome = CHROME_CANDIDATES.find((candidate) => existsSync(candidate)); - if (chrome) { - renderWithSystemChrome({ chrome, htmlPath, images, outputPath, platform }); - return; - } + if (platform === 'macos') clearMacNotificationBanners(); + rmSync(outputPath, { force: true }); try { const { chromium } = await import('@playwright/test'); @@ -1511,13 +1615,17 @@ async function screenshotHtml({ await browser.close(); } } catch (err) { - throw new Error( - `No contact sheet renderer found. System Chrome is unavailable and Playwright failed: ${formatError(err)}`, - ); + const chrome = CHROME_CANDIDATES.find((candidate) => existsSync(candidate)); + if (chrome) { + await renderWithSystemChrome({ chrome, htmlPath, images, outputPath, platform }); + return; + } + + throw new Error(`No contact sheet renderer found. Playwright failed: ${formatError(err)}`); } } -function renderWithSystemChrome({ +async function renderWithSystemChrome({ chrome, htmlPath, images, @@ -1529,27 +1637,134 @@ function renderWithSystemChrome({ images: string[]; outputPath: string; platform: Platform; -}): void { +}): Promise { const width = platform === 'ios' ? 1600 : 1800; const height = estimateContactSheetHeight({ images, platform, width }); - const result = spawnSync( + const userDataDir = mkdtempSync(resolve(tmpdir(), 'packrat-contact-sheet-chrome-')); + const child = spawn( chrome, [ '--headless=new', '--disable-gpu', + '--disable-background-networking', + '--disable-breakpad', + '--disable-crash-reporter', + '--disable-extensions', + '--disable-notifications', + '--deny-permission-prompts', '--hide-scrollbars', + '--no-default-browser-check', + '--no-first-run', + `--user-data-dir=${userDataDir}`, `--window-size=${width},${height}`, `--screenshot=${outputPath}`, pathToFileURL(htmlPath).href, ], - { encoding: 'utf8', timeout: CONTACT_SHEET_RENDER_TIMEOUT_MS }, + { detached: true, stdio: ['ignore', 'pipe', 'pipe'] }, ); + let stdout = ''; + let stderr = ''; + child.stdout?.on('data', (chunk) => { + stdout += chunk.toString(); + }); + child.stderr?.on('data', (chunk) => { + stderr += chunk.toString(); + }); - if (result.status !== 0) { - throw new Error( - `Chrome screenshot failed: ${result.stderr || result.stdout || `exit ${result.status}`}`, - ); + const exitPromise = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>( + (resolveExit, rejectExit) => { + child.once('error', rejectExit); + child.once('exit', (code, signal) => resolveExit({ code, signal })); + }, + ); + + try { + const startedAt = Date.now(); + while (Date.now() - startedAt < CONTACT_SHEET_RENDER_TIMEOUT_MS) { + if (fileIsNonEmpty(outputPath)) { + await sleep(250); + await stopProcessGroup({ pid: child.pid, exitPromise }); + return; + } + + const exitResult = await Promise.race([exitPromise, sleep(250).then(() => null)]); + if (exitResult) { + if (exitResult.code === 0 && fileIsNonEmpty(outputPath)) return; + throw new Error( + `Chrome screenshot failed: ${stderr || stdout || `exit ${exitResult.code ?? exitResult.signal}`}`, + ); + } + } + + await stopProcessGroup({ pid: child.pid, exitPromise, signal: 'SIGKILL' }); + throw new Error(`Chrome screenshot timed out after ${CONTACT_SHEET_RENDER_TIMEOUT_MS}ms`); + } finally { + rmSync(userDataDir, { recursive: true, force: true }); + } +} + +function fileIsNonEmpty(filePath: string): boolean { + try { + return statSync(filePath).size > 0; + } catch { + return false; + } +} + +async function stopProcessGroup({ + pid, + exitPromise, + signal = 'SIGTERM', +}: { + pid: number | undefined; + exitPromise: Promise; + signal?: NodeJS.Signals; +}): Promise { + if (!pid) return; + + try { + process.kill(-pid, signal); + } catch { + try { + process.kill(pid, signal); + } catch { + return; + } } + + await Promise.race([ + exitPromise.catch(() => undefined), + sleep(signal === 'SIGKILL' ? 500 : 1_000), + ]); +} + +function clearMacNotificationBanners(): void { + if (process.platform !== 'darwin') return; + spawnSync( + 'osascript', + [ + '-e', + `tell application "System Events" + tell process "NotificationCenter" + repeat with notificationWindow in windows + repeat with candidate in entire contents of notificationWindow + try + if subrole of candidate is "AXNotificationCenterAlert" then + repeat with candidateAction in actions of candidate + try + if name of candidateAction is "Close" then perform candidateAction + end try + end repeat + end if + end try + end repeat + end repeat + end tell +end tell`, + ], + { encoding: 'utf8', timeout: 5_000 }, + ); + spawnSync('killall', ['NotificationCenter'], { encoding: 'utf8', timeout: 5_000 }); } function estimateContactSheetHeight({ @@ -1563,21 +1778,24 @@ function estimateContactSheetHeight({ }): number { const horizontalPadding = 64; const gridGap = 18; - const cardWidth = platform === 'watch' ? 220 : platform === 'ios' ? 300 : 520; + const cardWidth = platform === 'watch' ? 220 : platform === 'macos' ? 520 : 300; const columns = Math.max( 1, Math.floor((width - horizontalPadding + gridGap) / (cardWidth + gridGap)), ); + const renderedCardWidth = Math.floor( + (width - horizontalPadding - gridGap * (columns - 1)) / columns, + ); const cardHeights = images.map((image) => { const size = readImageSize(image); if (!size) return platform === 'watch' ? 280 : platform === 'ios' ? 720 : 420; - return Math.ceil((size.height / size.width) * cardWidth) + 42; + return Math.ceil((size.height / size.width) * renderedCardWidth) + 64; }); const rows: number[] = []; for (let index = 0; index < cardHeights.length; index += columns) { rows.push(Math.max(...cardHeights.slice(index, index + columns))); } - return Math.max(1200, 116 + rows.reduce((sum, row) => sum + row, 0) + gridGap * rows.length); + return Math.max(1200, 160 + rows.reduce((sum, row) => sum + row, 0) + gridGap * rows.length); } function readImageSize(image: string): { width: number; height: number } | null { diff --git a/apps/swift/scripts/lib/args.ts b/apps/swift/scripts/lib/args.ts index 10da458055..2b6aa54822 100644 --- a/apps/swift/scripts/lib/args.ts +++ b/apps/swift/scripts/lib/args.ts @@ -10,10 +10,16 @@ const KNOWN_PLANS: TestPlanName[] = ['iOS-Full', 'iOS-Smoke']; const ALIASES: Record = { full: 'iOS-Full', smoke: 'iOS-Smoke', + 'ios-ui': 'iOS-Full', 'ios-full': 'iOS-Full', 'ios-smoke': 'iOS-Smoke', }; +const POSITIONAL_MODES: Record = { + unit: { passthrough: ['-only-testing:PackRatTests'] }, + 'ios-unit': { passthrough: ['-only-testing:PackRatTests'] }, +}; + export class ArgsError extends Error { constructor(message: string) { super(message); @@ -55,6 +61,17 @@ export function parseArgs(argv: readonly string[]): ParsedArgs { plan = resolvePlan(a.slice('--plan='.length)); continue; } + const positionalPlan = ALIASES[a.toLowerCase()]; + if (positionalPlan) { + plan = positionalPlan; + continue; + } + const positionalMode = POSITIONAL_MODES[a.toLowerCase()]; + if (positionalMode) { + plan = positionalMode.plan; + passthrough.push(...positionalMode.passthrough); + continue; + } passthrough.push(a); } return { plan, passthrough }; diff --git a/package.json b/package.json index dde6d46276..3a127115f2 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,11 @@ "clean": "bun run .github/scripts/clean.ts", "configure:deps": "bun run .github/scripts/configure-deps.ts", "e2e:swift": "bun run apps/swift/scripts/run-e2e.ts", - "e2e:swift:ios": "bun run apps/swift/scripts/run-e2e.ts ios-ui", - "e2e:swift:ios-smoke": "bun run apps/swift/scripts/run-e2e.ts ios-smoke", - "e2e:swift:mac": "bun run apps/swift/scripts/run-e2e.ts mac-build", - "e2e:swift:mac-smoke": "bun run apps/swift/scripts/run-e2e.ts mac-smoke", - "e2e:swift:mac-ui": "bun run apps/swift/scripts/run-e2e.ts mac-ui", + "e2e:swift:ios": "bun run apps/swift/scripts/run-e2e.ts --plan full", + "e2e:swift:ios-smoke": "bun run apps/swift/scripts/run-e2e.ts --plan smoke", + "e2e:swift:mac": "bun run apps/swift/scripts/run-e2e-macos.ts --plan smoke", + "e2e:swift:mac-smoke": "bun run apps/swift/scripts/run-e2e-macos.ts --plan smoke", + "e2e:swift:mac-ui": "bun run apps/swift/scripts/run-e2e-macos.ts --plan full", "e2e:swift:macos": "bun run apps/swift/scripts/run-e2e-macos.ts", "env": "bun run .github/scripts/env.ts", "expo": "cd apps/expo && bun start", From 9c9baa11c5164141bc531beaa2a22db0f602cd6f Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Wed, 15 Jul 2026 16:01:58 -0600 Subject: [PATCH 03/81] =?UTF-8?q?=F0=9F=A7=B1=20Bound=20Swift=20CI=20smoke?= =?UTF-8?q?=20runtime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index 6bc0d19800..47b15113e4 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -27,6 +27,7 @@ jobs: build-and-smoke: name: ${{ matrix.scheme }} (smoke) runs-on: macos-15 + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -72,6 +73,7 @@ jobs: xcrun simctl bootstatus "$DEVICE_ID" - name: Run ${{ matrix.plan }} test plan + timeout-minutes: 20 env: E2E_EMAIL: ${{ secrets.E2E_EMAIL }} E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }} From 2da5e8bb1f8d7fc40b4bde5f3fb08fddb6843d41 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Wed, 15 Jul 2026 16:24:59 -0600 Subject: [PATCH 04/81] =?UTF-8?q?=F0=9F=A7=B1=20Pin=20Swift=20CI=20simulat?= =?UTF-8?q?or=20destination?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index 47b15113e4..6b490df188 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -71,6 +71,7 @@ jobs: "$(xcrun simctl list runtimes -j | jq -r '.runtimes[] | select(.identifier | contains("iOS-")) | .identifier' | sort -V | tail -1)") xcrun simctl boot "$DEVICE_ID" xcrun simctl bootstatus "$DEVICE_ID" + echo "IOS_SIMULATOR_ID=$DEVICE_ID" >> "$GITHUB_ENV" - name: Run ${{ matrix.plan }} test plan timeout-minutes: 20 @@ -86,10 +87,15 @@ jobs: else CODE_SIGN_ARGS=() fi + DESTINATION="${{ matrix.destination }}" + if [ "${{ matrix.scheme }}" = "PackRat-iOS" ]; then + DESTINATION="platform=iOS Simulator,id=${IOS_SIMULATOR_ID}" + fi + echo "Testing ${{ matrix.scheme }} on ${DESTINATION}" xcodebuild test \ -scheme "${{ matrix.scheme }}" \ - -destination "${{ matrix.destination }}" \ + -destination "${DESTINATION}" \ -testPlan "${{ matrix.plan }}" \ -resultBundlePath "/tmp/${{ matrix.scheme }}-smoke.xcresult" \ -quiet \ From 1431139177ffc4675d73ac63b29b4bb94979d4bd Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Wed, 15 Jul 2026 16:52:02 -0600 Subject: [PATCH 05/81] =?UTF-8?q?=F0=9F=90=9B=20Address=20Swift=20E2E=20re?= =?UTF-8?q?view=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Tests/PackRatUITests/HomeTileTests.swift | 12 +++- .../VisualScreenshotTests.swift | 55 +++++-------------- apps/swift/scripts/__tests__/args.test.ts | 14 +++++ .../scripts/capture-visual-screenshots.ts | 9 ++- apps/swift/scripts/lib/args.ts | 2 +- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/apps/swift/Tests/PackRatUITests/HomeTileTests.swift b/apps/swift/Tests/PackRatUITests/HomeTileTests.swift index bd8540a256..61f616342e 100644 --- a/apps/swift/Tests/PackRatUITests/HomeTileTests.swift +++ b/apps/swift/Tests/PackRatUITests/HomeTileTests.swift @@ -99,8 +99,16 @@ final class HomeTileTests: AppUITestCase { private func destinationExists(_ title: String, timeout: TimeInterval) -> Bool { #if os(macOS) - return app.staticTexts[title].waitForExistence(timeout: timeout) - || app.navigationBars[title].waitForExistence(timeout: 1) + let staticText = app.staticTexts[title] + let navigationBar = app.navigationBars[title] + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if staticText.exists || navigationBar.exists { + return true + } + Thread.sleep(forTimeInterval: 0.1) + } + return staticText.exists || navigationBar.exists #else return app.navigationBars[title].waitForExistence(timeout: timeout) #endif diff --git a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift index 07e49e52e6..5b04cdec1d 100644 --- a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift +++ b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift @@ -582,20 +582,24 @@ final class VisualScreenshotTests: XCTestCase { let baselineName = name.hasPrefix("home-before-") ? name : "home-before-\(name)" openHomeForActionBaseline(name: baselineName) - let identifier = "home_action_\(title.lowercased().filter { $0.isLetter || $0.isNumber })" - if prefersHomeSearch(for: title) { - openHomeActionUsingSearch(title: title, identifier: identifier) + let verifyAndCapture = { if let destinationIdentifier { - let destination = app.descendants(matching: .any).matching(identifier: destinationIdentifier).firstMatch + let destination = self.app.descendants(matching: .any).matching(identifier: destinationIdentifier).firstMatch XCTAssertTrue( destination.waitForExistence(timeout: 5), "Expected Home action '\(title)' to open '\(destinationIdentifier)' for screenshot \(name)" ) } - capture(name) + self.capture(name) if dismissAfterCapture { - dismissPhoneDestination() + self.dismissPhoneDestination() } + } + + let identifier = "home_action_\(title.lowercased().filter { $0.isLetter || $0.isNumber })" + if prefersHomeSearch(for: title) { + openHomeActionUsingSearch(title: title, identifier: identifier) + verifyAndCapture() return } @@ -608,17 +612,7 @@ final class VisualScreenshotTests: XCTestCase { } if action.exists, action.isHittable, actionIsClearOfBottomBar(action) { activate(action) - if let destinationIdentifier { - let destination = app.descendants(matching: .any).matching(identifier: destinationIdentifier).firstMatch - XCTAssertTrue( - destination.waitForExistence(timeout: 5), - "Expected Home action '\(title)' to open '\(destinationIdentifier)' for screenshot \(name)" - ) - } - capture(name) - if dismissAfterCapture { - dismissPhoneDestination() - } + verifyAndCapture() return } if action.exists, action.frame.minY < 140 { @@ -629,32 +623,12 @@ final class VisualScreenshotTests: XCTestCase { } if let visibleCandidate, visibleCandidate.exists, visibleCandidate.isHittable { activate(visibleCandidate) - if let destinationIdentifier { - let destination = app.descendants(matching: .any).matching(identifier: destinationIdentifier).firstMatch - XCTAssertTrue( - destination.waitForExistence(timeout: 5), - "Expected Home action '\(title)' to open '\(destinationIdentifier)' for screenshot \(name)" - ) - } - capture(name) - if dismissAfterCapture { - dismissPhoneDestination() - } + verifyAndCapture() return } openHomeActionUsingSearch(title: title, identifier: identifier) - if let destinationIdentifier { - let destination = app.descendants(matching: .any).matching(identifier: destinationIdentifier).firstMatch - XCTAssertTrue( - destination.waitForExistence(timeout: 5), - "Expected Home action '\(title)' to open '\(destinationIdentifier)' for screenshot \(name)" - ) - } - capture(name) - if dismissAfterCapture { - dismissPhoneDestination() - } + verifyAndCapture() } private func prefersHomeSearch(for title: String) -> Bool { @@ -1365,8 +1339,9 @@ final class VisualScreenshotTests: XCTestCase { @discardableResult private func dismissInterruption(in container: XCUIElement) -> Bool { #if os(macOS) + let allButtons = container.buttons.allElementsBoundByIndex for label in ["Remind Me Later", "Not Now", "Continue", "OK", "Allow", "Dismiss", "Close"] { - let matchingButtons = container.buttons.allElementsBoundByIndex + let matchingButtons = allButtons .filter { $0.label == label || $0.identifier == label } if let button = matchingButtons.first(where: { $0.exists && $0.isHittable }) ?? matchingButtons.first(where: { $0.exists }) { button.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).click() diff --git a/apps/swift/scripts/__tests__/args.test.ts b/apps/swift/scripts/__tests__/args.test.ts index 23cadda01c..1263fc4a73 100644 --- a/apps/swift/scripts/__tests__/args.test.ts +++ b/apps/swift/scripts/__tests__/args.test.ts @@ -37,6 +37,20 @@ describe('parseArgs', () => { }); }); + it('preserves an explicit plan before a unit positional mode', () => { + expect(parseArgs(['--plan', 'smoke', 'unit'])).toEqual({ + plan: 'iOS-Smoke', + passthrough: ['-only-testing:PackRatTests'], + }); + }); + + it('preserves an explicit plan after a unit positional mode', () => { + expect(parseArgs(['ios-unit', '--plan=full'])).toEqual({ + plan: 'iOS-Full', + passthrough: ['-only-testing:PackRatTests'], + }); + }); + it('case-insensitive alias matching', () => { expect(parseArgs(['--plan', 'SMOKE']).plan).toBe('iOS-Smoke'); expect(parseArgs(['--plan', 'FULL']).plan).toBe('iOS-Full'); diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index a031281f5f..89fe1e07e2 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -789,11 +789,13 @@ function pickAvailableIOSDestination({ timeout: 10_000, maxBuffer: 10 * 1024 * 1024, }); + let inventorySucceeded = false; if (result.status === 0) { try { const parsed = safeJsonParse<{ devices?: Record>; }>(result.stdout, { strict: true }); + inventorySucceeded = true; const availableDevices = Object.values(parsed.devices ?? {}).flat(); for (const preferredName of preferredNames) { const preferred = availableDevices.find( @@ -809,7 +811,7 @@ function pickAvailableIOSDestination({ } } catch {} } - if (createIfMissing) { + if (createIfMissing && inventorySucceeded) { const createdDeviceId = createIOSSimulator(createIfMissing); if (createdDeviceId) return `platform=iOS Simulator,id=${createdDeviceId}`; } @@ -1682,8 +1684,9 @@ async function renderWithSystemChrome({ const startedAt = Date.now(); while (Date.now() - startedAt < CONTACT_SHEET_RENDER_TIMEOUT_MS) { if (fileIsNonEmpty(outputPath)) { - await sleep(250); - await stopProcessGroup({ pid: child.pid, exitPromise }); + const exitResult = await Promise.race([exitPromise, sleep(1_000).then(() => null)]); + if (exitResult?.code === 0) return; + await stopProcessGroup({ pid: child.pid, exitPromise, signal: 'SIGKILL' }); return; } diff --git a/apps/swift/scripts/lib/args.ts b/apps/swift/scripts/lib/args.ts index 2b6aa54822..8b68f65e15 100644 --- a/apps/swift/scripts/lib/args.ts +++ b/apps/swift/scripts/lib/args.ts @@ -68,7 +68,7 @@ export function parseArgs(argv: readonly string[]): ParsedArgs { } const positionalMode = POSITIONAL_MODES[a.toLowerCase()]; if (positionalMode) { - plan = positionalMode.plan; + if (positionalMode.plan) plan = positionalMode.plan; passthrough.push(...positionalMode.passthrough); continue; } From c3a42f007bf7d03cd62c7ba4f8bb559d6a146444 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Wed, 15 Jul 2026 20:29:21 -0600 Subject: [PATCH 06/81] =?UTF-8?q?=F0=9F=94=90=20Align=20Swift=20release=20?= =?UTF-8?q?signing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Resources/Info-iOS.plist | 17 +++++++++++++++-- apps/swift/project.yml | 13 +++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/swift/Resources/Info-iOS.plist b/apps/swift/Resources/Info-iOS.plist index 1d5666e420..f1debf4f8a 100644 --- a/apps/swift/Resources/Info-iOS.plist +++ b/apps/swift/Resources/Info-iOS.plist @@ -47,10 +47,10 @@ CFBundleVersion 1 - ITSAppUsesNonExemptEncryption - GOOGLE_IOS_CLIENT_ID 993694750638-97t0vhfml04u2avrlbve22jbs9qcinbc.apps.googleusercontent.com + ITSAppUsesNonExemptEncryption + LSRequiresIPhoneOS NSAppTransportSecurity @@ -74,5 +74,18 @@ UIColorName + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + diff --git a/apps/swift/project.yml b/apps/swift/project.yml index 0a0219047d..3a53193f4b 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -77,6 +77,15 @@ targets: UIApplicationSupportsMultipleScenes: true NSLocationWhenInUseUsageDescription: "This app needs access to your location while you are using it." ITSAppUsesNonExemptEncryption: false + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + UISupportedInterfaceOrientations~ipad: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationPortraitUpsideDown + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight PACKRAT_ENV: $(PACKRAT_ENV) SENTRY_DSN: $(SENTRY_DSN) GOOGLE_IOS_CLIENT_ID: 993694750638-97t0vhfml04u2avrlbve22jbs9qcinbc.apps.googleusercontent.com @@ -124,7 +133,7 @@ targets: MARKETING_VERSION: "1.0" CURRENT_PROJECT_VERSION: "1" CODE_SIGN_STYLE: Automatic - DEVELOPMENT_TEAM: 7WV9JYCW55 + DEVELOPMENT_TEAM: 666HGMV2LU PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat PRODUCT_MODULE_NAME: PackRat TARGETED_DEVICE_FAMILY: "1,2" @@ -151,7 +160,7 @@ targets: MARKETING_VERSION: "1.0" CURRENT_PROJECT_VERSION: "1" CODE_SIGN_STYLE: Automatic - DEVELOPMENT_TEAM: 7WV9JYCW55 + DEVELOPMENT_TEAM: 666HGMV2LU PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.watchkitapp PRODUCT_MODULE_NAME: PackRatWatch From e30b6556e63355366d66eff5a8f9dd15d095581f Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Wed, 15 Jul 2026 21:21:07 -0600 Subject: [PATCH 07/81] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Label=20Swift=20b?= =?UTF-8?q?eta=20builds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Resources/Info-iOS.plist | 6 ++--- apps/swift/Resources/Info-macOS.plist | 6 ++--- apps/swift/Resources/Info-watchOS.plist | 6 ++--- apps/swift/project.yml | 30 ++++++++++++------------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/swift/Resources/Info-iOS.plist b/apps/swift/Resources/Info-iOS.plist index f1debf4f8a..0bfa2b7b2b 100644 --- a/apps/swift/Resources/Info-iOS.plist +++ b/apps/swift/Resources/Info-iOS.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - PackRat + PackRat Swift CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 2.0.28 CFBundleURLTypes @@ -46,7 +46,7 @@ CFBundleVersion - 1 + 2026071601 GOOGLE_IOS_CLIENT_ID 993694750638-97t0vhfml04u2avrlbve22jbs9qcinbc.apps.googleusercontent.com ITSAppUsesNonExemptEncryption diff --git a/apps/swift/Resources/Info-macOS.plist b/apps/swift/Resources/Info-macOS.plist index 1c76dceb2a..6545646a29 100644 --- a/apps/swift/Resources/Info-macOS.plist +++ b/apps/swift/Resources/Info-macOS.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - PackRat + PackRat Swift CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 2.0.28 CFBundleVersion - 1 + 2026071601 NSAppTransportSecurity NSAllowsLocalNetworking diff --git a/apps/swift/Resources/Info-watchOS.plist b/apps/swift/Resources/Info-watchOS.plist index 87768fc109..9af12ebb0d 100644 --- a/apps/swift/Resources/Info-watchOS.plist +++ b/apps/swift/Resources/Info-watchOS.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - PackRat + PackRat Swift CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 2.0.28 CFBundleVersion - 1 + 2026071601 ITSAppUsesNonExemptEncryption WKApplication diff --git a/apps/swift/project.yml b/apps/swift/project.yml index 3a53193f4b..90226b8732 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -67,9 +67,9 @@ targets: info: path: Resources/Info-iOS.plist properties: - CFBundleDisplayName: PackRat - CFBundleShortVersionString: "1.0" - CFBundleVersion: "1" + CFBundleDisplayName: PackRat Swift + CFBundleShortVersionString: "2.0.28" + CFBundleVersion: "2026071601" LSRequiresIPhoneOS: true UILaunchScreen: UIColorName: "" @@ -130,8 +130,8 @@ targets: settings: base: SWIFT_VERSION: "5.9" - MARKETING_VERSION: "1.0" - CURRENT_PROJECT_VERSION: "1" + MARKETING_VERSION: "2.0.28" + CURRENT_PROJECT_VERSION: "2026071601" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat @@ -148,17 +148,17 @@ targets: info: path: Resources/Info-watchOS.plist properties: - CFBundleDisplayName: PackRat - CFBundleShortVersionString: "1.0" - CFBundleVersion: "1" + CFBundleDisplayName: PackRat Swift + CFBundleShortVersionString: "2.0.28" + CFBundleVersion: "2026071601" WKApplication: true WKCompanionAppBundleIdentifier: com.andrewbierman.packrat ITSAppUsesNonExemptEncryption: false settings: base: SWIFT_VERSION: "5.9" - MARKETING_VERSION: "1.0" - CURRENT_PROJECT_VERSION: "1" + MARKETING_VERSION: "2.0.28" + CURRENT_PROJECT_VERSION: "2026071601" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.watchkitapp @@ -181,9 +181,9 @@ targets: info: path: Resources/Info-macOS.plist properties: - CFBundleDisplayName: PackRat - CFBundleShortVersionString: "1.0" - CFBundleVersion: "1" + CFBundleDisplayName: PackRat Swift + CFBundleShortVersionString: "2.0.28" + CFBundleVersion: "2026071601" NSPrincipalClass: NSApplication NSHighResolutionCapable: true PACKRAT_ENV: $(PACKRAT_ENV) @@ -212,8 +212,8 @@ targets: settings: base: SWIFT_VERSION: "5.9" - MARKETING_VERSION: "1.0" - CURRENT_PROJECT_VERSION: "1" + MARKETING_VERSION: "2.0.28" + CURRENT_PROJECT_VERSION: "2026071601" CODE_SIGN_STYLE: Manual DEVELOPMENT_TEAM: "" CODE_SIGN_IDENTITY: "-" From 9d1e58ac93c79c03a479bc645ae597bbba376f47 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Wed, 15 Jul 2026 21:59:58 -0600 Subject: [PATCH 08/81] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Prepare=20Swift?= =?UTF-8?q?=20beta=20TestFlight=20assets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppIcon.appiconset/AppIcon-iPad-20.png | Bin 0 -> 2235 bytes .../AppIcon.appiconset/AppIcon-iPad-20@2x.png | Bin 0 -> 3604 bytes .../AppIcon.appiconset/AppIcon-iPad-29.png | Bin 0 -> 2788 bytes .../AppIcon.appiconset/AppIcon-iPad-29@2x.png | Bin 0 -> 5300 bytes .../AppIcon.appiconset/AppIcon-iPad-40.png | Bin 0 -> 3604 bytes .../AppIcon.appiconset/AppIcon-iPad-40@2x.png | Bin 0 -> 7943 bytes .../AppIcon.appiconset/AppIcon-iPad-76.png | Bin 0 -> 7461 bytes .../AppIcon.appiconset/AppIcon-iPad-76@2x.png | Bin 0 -> 19755 bytes .../AppIcon-iPad-83.5@2x.png | Bin 0 -> 22781 bytes .../AppIcon-iPhone-20@2x.png | Bin 0 -> 3604 bytes .../AppIcon-iPhone-20@3x.png | Bin 0 -> 5518 bytes .../AppIcon-iPhone-29@2x.png | Bin 0 -> 5300 bytes .../AppIcon-iPhone-29@3x.png | Bin 0 -> 8512 bytes .../AppIcon-iPhone-40@2x.png | Bin 0 -> 7943 bytes .../AppIcon-iPhone-40@3x.png | Bin 0 -> 13957 bytes .../AppIcon-iPhone-60@2x.png | Bin 0 -> 13957 bytes .../AppIcon-iPhone-60@3x.png | Bin 0 -> 25796 bytes .../AppIcon-watch-launcher-40@2x.png | Bin 0 -> 7943 bytes .../AppIcon-watch-launcher-44@2x.png | Bin 0 -> 8402 bytes .../AppIcon-watch-launcher-50@2x.png | Bin 0 -> 10418 bytes .../AppIcon-watch-longlook-44x44@2x.png | Bin 0 -> 8402 bytes .../AppIcon-watch-longlook-50x50@2x.png | Bin 0 -> 10418 bytes .../AppIcon-watch-marketing-1024.png | Bin 0 -> 618576 bytes .../AppIcon-watch-notification-24@2x.png | Bin 0 -> 4146 bytes .../AppIcon-watch-notification-27.5@2x.png | Bin 0 -> 4882 bytes .../AppIcon-watch-quicklook-108@2x.png | Bin 0 -> 34858 bytes .../AppIcon-watch-quicklook-44@2x.png | Bin 0 -> 8402 bytes .../AppIcon-watch-quicklook-50@2x.png | Bin 0 -> 10418 bytes .../AppIcon-watch-quicklook-86@2x.png | Bin 0 -> 23984 bytes .../AppIcon-watch-quicklook-98@2x.png | Bin 0 -> 29523 bytes .../AppIcon-watch-settings-29@2x.png | Bin 0 -> 5300 bytes .../AppIcon-watch-settings-29@3x.png | Bin 0 -> 8512 bytes .../AppIcon.appiconset/Contents.json | 206 +++++++++++++++++- apps/swift/Resources/Info-iOS.plist | 4 +- apps/swift/Resources/Info-macOS.plist | 4 +- apps/swift/Resources/Info-watchOS.plist | 22 +- apps/swift/project.yml | 70 +++++- .../__tests__/app-store-assets.test.ts | 28 +++ apps/swift/scripts/lib/app-store-assets.ts | 2 +- 39 files changed, 320 insertions(+), 16 deletions(-) create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-20.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-20@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-29.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-29@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-40.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-40@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-76.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-76@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-83.5@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-20@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-20@3x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-29@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-29@3x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-40@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-40@3x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-60@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-60@3x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-launcher-40@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-launcher-44@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-launcher-50@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-longlook-44x44@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-longlook-50x50@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-marketing-1024.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-notification-24@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-notification-27.5@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-108@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-44@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-50@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-86@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-98@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-settings-29@2x.png create mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-settings-29@3x.png diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-20.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-20.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d3ce67677b44da12b462f2a172a2b1e37e2704 GIT binary patch literal 2235 zcmZ`)2~bm46n%-J?8+i2NR5G_AmoRLL}Ew-1SG~V$kyOgBqTo)`B_MQOvDWk6%auj zP(VNwwSq-QY_&zuQl*Gku{`2m+_uTjH`$-Q8UTkJ; zXAA%^;|6g;vDq3QhD7X_*qN~$nQphMmu_6|x=wwQq z1(-SpX3AhxNYcp=xtgK#A?rODn2o~_nWT3?<9)~iehA55p@K=A+l?({dYPA&Ye2PM~3i9yw_J*i5h(>eAJlxf5<)~2SE>}B#4e~7x z2Ud$zQY9)?$VqryVXQ)f`jE+ZqQPfCr%XARNUk2J3aby&36+osg$j*DLZy-+c_Uc( z%D6OGCmn}|uZ&9*iv|@cH7Z2E5n>SpBd`pXqiT%j0S(rVmB++Rg5j@%g{VRmu23K> zeK#VK_L9fbR<< zM5==k0Y{2etH#8!Jg78JCN$>KXsW*gQK-U{LJ`dJ7@!P;MtkcMMU9_m*wQenSi}&0 z-E4g$5B8V7qr>2YktlvXcuOKu*5LJILIcQ9RNPIEjtszr#>~dVP*jQ{@W@>HM7{_* z5*<>2-!%pzl&|t3>)ge#M5sYfGAl$ViVqc{BzF>an&S%_8CNfCgoLl$VK-5);wy^X zbCxmIkl(%;EMrq~Zx8mBa^!M+!xvgagmL4nmQ63A7jO%2JIjLHWZjZZKiRcN6N9=G z@(XT#ihV|OP57%vT&B^3H>AXWtBWRDa1yQdwE3!@9ZDz<;8sN0Ek7fEE-ny1t)+kI z`9~bLtzj>{|I@?%4gI}4-#j|aHshqTkFd+xhOvu&bGx5rn7lHr*qwfjEDua6-zwH! zs*SQXY}mcS^9PqCnUyxSD0Ahl^dAG2H$5{omyftji?p6fR(X&b{R%H>%=VcXNEBH$ zXRkhd*mcM14cky)ZFQ+EpZI;rWt(6n?b6-)$KAzW7=k%{#H8xC5>_vLPc&qgFPWB? zyx+LDHEOnlAs_BS0D2m1x`y;`g+ zq!N!)-nX)D-gv3@nF84@aH=_)&)hhxc>5~86IFdD*N^trGLd$+Q>iH9SuXHfm-3QV zR++YW_J#JJw=`y-_{cdGY~5AD4H2aIQj_2G75Y?d{yZ_}6U7u0*}cBQd%GgEs`+^Fql(Zem4H>pnrbLQ+n)mK%~ z;T7KRb!XBZ*AMq^H#aBQXI^Q(u{5Fds8QTyan8Cn2M*0pvnVw*D*iQNW%R5S%@wmG z*YYa9OF!l?1DVnF&OZ0ST92@~;cQ}ke$L6+kF@~@e?4$M;xB)Py85@hI~og?o<%A) z6keX`uzH8{$&3R_h#^&-^>(I}yWa1kpPBq{>Vnj=T(>^-N;ZyO!8s_jt?9 zDd*13Yz#E1sO?yi{2p~OTQtwb)?h_KRZgn$y&_X$Zq41@rcLeb9q_p*N4F==PCe$? a|IL~?znEtm*4K{z?Q#QxImi5BQvL^oB=*n% literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-20@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe258e8108debb378fe247b1e295646de3407add GIT binary patch literal 3604 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 U=wZ{K=>N<%+wC^FR$fQ`53L8hF8}}l literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-29.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-29.png new file mode 100644 index 0000000000000000000000000000000000000000..3cb25420f2759883422261a8c443d0c5c569b88e GIT binary patch literal 2788 zcmZ`*3p|r;8-Ipghe!t^hnk6;b|8saW6YtkLio0%tnC@oY}y_+MN&yANuP5Og_T~7 z(wmPCUrEKUsN~grEpJC}ycJ$jMBlUJb?8^m?|1LMp8tLQuj_x^_jNzN9Sm6KqoZY{ z1pt7~T3>PyJew+ph6?;E8|bIOlUgK+N&I6;06Q!JVD~-%7=@wT ze*!=p4gh*1005s400yxq>H`VzLJ3G)%cfER8`#zWenA)kiUa-2Bf!gE7_u)x1)|V~>fO)qR1Ce7p&ls*floL#YXjFeZlOKa&u=ra+ zj5sD%!2$?kJZ#2*5CbWW;qrud@fwuU0}tB@7>hzGU7&4iP&8@)(vvR$kxm#K28SYQ zA(2RefW^iKk-aDB@Y@ZMu~?Bvgb}U8@CA`rdlwfMEY1Pz;DClb(82^B#1Nx- zLi2Ay{)s~dg-ih_7UJ-ENJU)6R(>3`28B{2ntCR6#>7q~;t40Kg6qSI8L?P<3=aDv z62xIo%bUegto)P)ia9@_DOP?;V=<=`#>NS_%8g(#u^<W9lCz$bzC z6o3qfF9_!IxkP0*5U}6F)4M@X$i@eOLOwT6v1uVZ4n%YyV1ERE&!SD!h%Qc(oSESF z1p1E%Gr<`I#lGMJIAV}XBXi(tg|IlHJrlTs{l<4dL1ndv9 z5nvSJKwNNkE@dJT7n+SuD^T1uJeR?XT!Rv$SsdMndR0;Z|)z}?_j*tE~R}=IN4WtpdzU3 z*;kZ>AH^@Vk0qjJd4Bq1@acnLFF>`+dN6pI3fWzIm%rnm5g%8C5jD%bIO!X$`p4T= z)yl(eZQtZ`X{1Py*Bv&0Q~Jo3VmYO`BvU2WNOvQ9g~s`;&^AtVdS$75P1vt!TQw_; zbVFi;t%Guqm`PcP1Gev7x6Tov!!?sICFb8b9>KY!>JUX(L3t9ij&Vt zcICB!FD|6C^*z}f+#j8c%)RTZ_H1A4z!Ei5+$xJBM~aHCx)RilC<6oWm&a@5;NBG1 zXCC&tdYLuox1VHNq!U;F(&GjP$(PbkBJ{#NmbDVXI$wx(n&2rLLyc{p8jRtQ=k-_D z85$eTO-eu~GT9w#^G~g(w=K9@l*qQOGqH0s%rs7Dz(hDIaHKT67|sbq#7op z+uEu+Fu<p{GTo;~SkioOhzO>KLXj2j-qA#^tdPx2;%_!+?HrbgTuWd?E-SQ&2 zo{gh+-rU((8s)L)*T&?k+JEsco;J(%ds&xV_C!1(KmD$%qX`%DKE1rMvJe@6m8}1u zBc<}+&+lW;V~dyQe7@RhZt7~6&#GJ6W`bYkaM?s^XPsEqV8uB3ujIlu*30;v!`oJKC0z z5z^G(M#dd`+EVvl8BN`)NVIfpzm0p)^DNi=eG+i~c@W5?qNN``j&D}qz2Sv~43o+f{q zA?sL_R@j58$+<)>4aYTBhaXnY(=}Ok)3V9ESr3es?@$|4ZEJMAr_C)q(Qsij4tb^e znALWf9c~7EoanGCl z&Qnsl#>({f`l|Llo9|n8DnNJDU>22OR^2w*T|M^N!ewo zlzqz@X|seR{D!Yz%XfX>dtGPF^WNvVpXYwgdCvRCM3|bKVPWKB1ONam`f!*zWmelC z^wgAJ?8rngWukT0Hr56JDq@(vBMwpC1zq9h#sENo7yxkPIsovE0$o`I0DNTtfF)-N zHyHrn!KKxkK`9HFC>wn@V`G2>#ij?)QSkxxBT!MsIjUnnZAU6`0L=kTNlPUQ*iTMJ z8E#bS2ll=^uN!Cp?ST&f;76JFA7{$gk99EXBCvi4fC0)MbWIaZy{T$lThG=7Kq~_|40V9y!E+{Yw zgWG2Tpd>Yli9rz&f+UO=mY_z`5IXQsquBc}L`d+!g?K?j$i~=IP}due5>x`qfMtX< z83hFep?IX5nmO$B4?5*dL&$?j#Hm3b{{H@8e|fMs-W?*Vs;UZ+k%P#|NmD$e2^XraI^U%c1BMj%}wC@&NSg(VUwJXy%k z`YGi>DJP+(i$@`d-grxIZ!gV*Zh%7m3IEy+=)P<eb%Xfd%tgvXJrW_RVZ}2}^ zHos_^s!BgNzk~lI*!~;gcknmD{=TT0qDd$(8yK2WErB9VQ&vV!0SfuorGHX&y}i8g z7C3|}N>la+c_&?Is?m_#36%b4^A{L;nApp~=XMT>T6H zu<`4|v@JajJv@IAmoNYfzPR1Ju?p9L(=#9uhNtMwxhv?wQRXlE5SD6-h0?WE+y%g+ zcl}CyubLGPn%SMXcg@1r$U&P+8fb=OO68&tai+cs*Wq5h-SF{J*%QL2$8T0A4Q~a1 zSxZg2^kU~OA!uQokhCe&vPH~cIV%(wSH2SL@zM!C*PX)*pK3CtqiU1n;$^8T73v2@ zNv$1`R}IZmBA!(s8k3?$TUjVbF4Bl=Bl**_iTaknZ= zP748xnwl!`Kj@#r2sP=x*t_k3fvKUF;PdS)6v{=Uln(ed+8ftwx%)<+B;6qg4UpCRe{7_{k* zb991tK(tqDrHfPQ{_+tOL6A&D7{2teoS6k~O(qb!!S{RC)Z`m6@At;4Uw{5^!FN=i znI{(>ggh!1BZW|9Xft%M5mT0kXo%ClUMVDXexQ?9Byr%>Ie0Q0=DH#q+Q^n9c@_L+NKYG4n4h&edU)$2Gxt7m! z9vP=O5=k^T7ivL0)pysW$m~pnO@+z8>Q7H^Y(LkT>FIo`X>bw9?K>Hgf%lB9K)ewxk2rCvvUtwRjNQ14)^~P= zls**q&c)((!b*p6+Xdo{Ee&C6N9k+_OES}~MpG&N0R`#k9oit>I2)<_zG!R_)x1I+ zPUS6_(-L{-UF|(Ur%;Cxiz?6~Gaf#!c3-9YqYQ~<{2p&Q;aPG>2ls%zhK~&*`q=2O zx#><^q2Fv&rBuwU<)sW7keFy3&6LZLgmiEj?769YZODmgYLP^$m5ub)vF#TVs>MX- z+wJg+L1KARSK* zyUv#nPOlBpX=)y&{!qE+3DtTC_1}&ty=}kQ9HGnNcnu4jBP~-mI&#;xdkDROxLFcR*&`fGS zNO%f~uEJ;zlOt{#Jd=#iy6C3@vAbt(7#JX1=*OEF%g>g`bJ=K z%aHeMX=7vkJA>60yIz|p*VW!DC%iVpTx&F*(TS!Uu44XFx zv9794juQ4RY-95sm#K&6mfnP(#IBD!vL2Svjbx9l6VrAPtQiJgsT3-8NY4j0_DdZT6v2njHtGJsUX9$k7_ z+;q?^ZiJ(-UoW09>A85=gX6{%oHrlDH{8U!I}r!e&j{4!FWv3}p{d+JY2SD6mpP4n zbGYPRsKwt>CE^2=N)eIIGU8NiuxCy?O^otTDI4CCU~C9(tY%zzH9a^PBIO^H*!{uA zyX^Zy-01DJlmx0Zs{9x+Ma|LRjTL{kS=#n{29MaTB-`5=*)H6qGrlU@&MtG2QIz}E z8?Ay8g%_{;crZ+-JtH67=8ydJUfr&_;_K3}_VXrVRi@mH_4BmR_V&GtUV|D2ZgWGO ze1(M#9Oi*reFLQAQ{P=?r)GDjQl{>(*P$6Mj}6*AS@_UhW5O?DJZpDkuN1!8a44tr(EtAud>~)Nz{);NR ztDES;Rx{0Qq>uRg>g1^B{rFacs9Bl7Ezkun?iYu&eNJNDG49kivi~K`k`E0Yxs12e zejC-LVBNu0!j(x~nr~VZ_n_QY8a>>$|X1h*DRHd7-X8Z+*EnGbnN-K ze_LUH)}C}72HV!U+91>TO!j13Dg!)1`?QIsgR^)7R2Z4AW)R=5B6uf~C57bkzDR^_ zVy92tQ3KZFmYtSpmQ1CvG>brVcuK*|i0IZl$V@6xm&_`}z*D za(}w7Exw)k#elw6IratlTiN_z>XIc8KzK{5J7c?22VXx05;`g?^65Lkbz(l?ZpQs& z)uNt^Rw)cm(f#d-c30_%E!Ff2QB_6ZRcyM3r=KzV^U0!at;IzKgz>SoS^-Nco3&fF zLgh-%YI}xfE7dmRShsM_Y9GrT@647;7zBIf%0;(Uc^IwL^9N=vmb!j~V7%;CHYZp* zUhuFzOH*tN_h)zsJY?{lZsummGj*DI$Z>1#n@1A#8owBc_x5f~Z9df2;8nVK1yS5) z?dPAr_B0}zMLpH$oXRpMZQwO(U2PsZmPrG8?A-B~;o{c>D>H-aQ#rfebY;0#leG)D zqnC4R?IT+ld4MZTtAU@c>9RGBmLsuN(Lm6*mx<;b^PGiHLeq1CL=qdXng ztD5Pt#q)<~eFkO*bmf)78I7Jnilvixo5Eix7VbncjH+EjSl&;TRC&w)I-o6m8M$*+ za>?LA7frs#Fzj^bVbcr7AkI<0->6p33nG;g{wDHVnU;a1FvlZOe~bz#_4%Rg zuZ~PDU(C6mvE#=FNA_0RTiC$qm%C?E@A}O}&$%UcNfy8KSEOk&t<2-$edzQ4KDdh! zS)V>a+oglw-LMBGAe!PdY@hQh6zK> zWRR-Bq$q8aoTwu>z$K(K(@>;e)A@Qc{qij*Yvm8ct_yK)&BKfN8yT-1nzxPw8@cx`(YxlkX zaZ>g$ELxP8(|XbTBRN~P)&oSDf#I+8wQYdUvhEifz+6X|BEHd>OZs!lTuc zOK#K6xDIk}8OBp&b}lWb|NLVtj|58+rvb05tSCF?d%*KN%=FDsXHZ~$D<-BN&iOj& zqwi$Uh`0tuB`tm8<=6g8H&*2wYW1c9Mu5yUzU#9YfjGvX>L;7=4h2ddJOb9Pb}u+U zXYQd5QokH?gZ?F_niKwGWw)Mv;t*`nk6sv@um*RJJ>b|sP8Bo9<(exlZTbnVG#6Qg5S>cxWa@T@B+ITUzrX~dW)UCgVwsrx>$C&&^1|RQ zHiX)IDYsZqWtc~1MPh-~LRmc)x`cSu`LxNx;UkZZxR2jyrCjS`8c>HGdz9A5M1#-v zvlxG+>8>ooAuW_PvUeGd2i^`pO!Vd!iegYCpTVDW1k&AQ<#kv8wvoP=Y?`i2@R~M$ za60hytH9j4$X$msON@GXI^3P9;u4uv$$t6wlWMr0>7V3?a?BEPVA7l5LnMPi#`Zw6 zbS%SH-LBA~YsPoi$H=j%8G~E{iH9Axz9b(NJv(={#sniE2R~(qw0f$dUZQi8&M0>J zi(N_9EN);zs=q|>RB0%K8@jOW!^STBLyks4jdJz0Q-7mMzd;q<6d4v$FBb!g-oa~y zO6srk=9qB+GN!lNwrPqge7htSiIEbVGvW9gE*V%e@@VSAu9QPYyt-Xv+X?$Yb1>rV zp0=Q#{K>LhbTg^^GK^WI3Ww5T&(bfg<%{c9VeyR~&(+yrDS-2z;EYWsF~fvd#4wH) z@K|kD2CwDvzzy-c*qF~SU%ci=+VmE{~HpZ MuV(@))o~8_9}6SmPXGV_ literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-40.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-40.png new file mode 100644 index 0000000000000000000000000000000000000000..fe258e8108debb378fe247b1e295646de3407add GIT binary patch literal 3604 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 U=wZ{K=>N<%+wC^FR$fQ`53L8hF8}}l literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-40@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..684e64feeee408d9235a2beab21fd8433890da3d GIT binary patch literal 7943 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 literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-83.5@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad-83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4a4070a38c4cebc55bfad6cbc6ff9f857e5ceeb6 GIT binary patch literal 22781 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 IM6N<$f=^SRX#fBK literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-20@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe258e8108debb378fe247b1e295646de3407add GIT binary patch literal 3604 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 U=wZ{K=>N<%+wC^FR$fQ`53L8hF8}}l literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-20@3x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..da807860803d45dd756de2eac6b9aa9608f13733 GIT binary patch literal 5518 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 MuV(@))o~8_9}6SmPXGV_ literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-29@3x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-29@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..64fcb30d1e1cd2728c3479296431578efce98376 GIT binary patch literal 8512 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 NE6YETE0Hk|`aeU$Dx&}Z literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-40@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..684e64feeee408d9235a2beab21fd8433890da3d GIT binary patch literal 7943 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 OsHDZ^#VSP%{Qe&yKK_gV literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-60@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..668ad96fc3278704e952c85fe40bc10316b66c8c GIT binary patch literal 13957 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 OsHDZ^#VSP%{Qe&yKK_gV literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-60@3x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-iPhone-60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f27bcc86671d4c8bf2d6510a9931839c49147677 GIT binary patch literal 25796 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 BaC!g$ literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-launcher-40@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-launcher-40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..684e64feeee408d9235a2beab21fd8433890da3d GIT binary patch literal 7943 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 EAEFuSfB*mh literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-longlook-44x44@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-longlook-44x44@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..18d0ccfe20858b5a95cc56e62de502cc53901ece GIT binary patch literal 8402 zcmZ`;WmuHm)*ht<6bT7oXq9FN5g1@lT0y$I1{k`#Vdzj=Qer^5yAh-tX^`&j<^#|B zy>*@QT-Tnxo_no()n5DgGl9~QqE9f0F#rI-llNjmvUj`k{d)B9?)T<2OYd$&HhlN- z9RN@qj(Mena`#T5Cnoz50C0W<0C@TW02gyya7J&2{zYF_-6>uLM^==tF;Q1}zr)TO9 z10esl0RZgp_WMWsZr%I(y$kA?+v@;6Kpmi_6!K6g0D$!M!5?1lY`#AHldb#}=`ZkiA z--%{WTOA5VGgEULup=MUZwv69d=G=DD1MvRn($F6f0U*Ww6KO!ykTZxW}$*$P*70t zTI(BtWralkpx?dmQ5o6VT7p3!2L}gc2X*AFO-L|D-`3VgG}6ulb)eeZ4<3w6wD}{oM$CJrL9sY6dm8wYlT5 zg8r=kt~}nmlK=}^Lv?H|tmQ2%Od-Fgffw{|_^)p8-lq+gh1yt{+TAxz!5n4_VdDk; zH~8Ny<-ceU?l*sM{to_|pz_}ce+U0YxbF*C8s-Q!RThHXRcmvX9E6pH?KLmxzpDOC z6|^w5u$Hsb(St%*|DgO0`nUD(Kq~(`(BD*lqw4E{_5RcB--G<;eEptiIjGe?(fjkc zA4yXf<_Z}guIctQU)8t)xy3$rza{xjC^K<`X#|B3#U!TntWo9dVw z@=-Z5=|c^4>`ZN`AksQ|CbBxV6igI%r}@7V`={UEiTxvm|8W0y6Mtv%AN1~?Lon_J z^8ddX5RAwA2fF|O3HZIxJ9)UEOK*zr=cOJaH1@{xFLHQWoo8UI&D0G}?B#lC_L9hPvBH;!nh z_=vpg5z2n7y_jV5do?4hf+}5*8Sjm^XIif7q-=WeA}z_N7YmNFxfzL8=7ID@S8mVh<~oAZ&dArUp)t-Gqu2O3ISMN>LRZ1k0sObf8u4o#E^9uUsL$r9ZE{ zge@Zdwe_^}8}zfdaPgQN-Y2v|)*@VWSBhBji$9*zMrr7EJd;A17b(s^>x!Tj0~+H* zvKB|PqMn(b8M`dJy554D`C|Zs+32;h--k9YGfa@;dr2r_jpqP~5ohUYCR;>mAeW$r zQ9b=5eO`fPV)TUM%ZPIm(1++EgYuUX*;YZEs;%9Gh#+n5msNZz#Zru0F`r?ITjov^ zZ@MtcJ&WXV_Rj-ilX0BoCj^BJ=#6@~*i_T3c@ukr>yC_YI8jNb9twVPp^Fz9F|rN+ zY4KivaX6@oQKzu_%i7EoeR?aW8c#_izi_}VR9L#kg5s8ev;)XT%L^Z7$>C?U@4wT+O*4&fK6VTNP9mrucWac*hG&&hp z;q-N(BB&qcn0?mqj5v&uwpZP4Fat4cbHElMDnebpE$C4IIX|jc+41@u^)+!(=9m?O zBta0~`l#9+81l?Owz+#O7CTY|Ch^ zdb`pEV!H8BK^0KU;ru}qjnj}BO;ySTf#Tji%Cm$U#Q9T62t@ylWq}v9rFAc*^^MAmd^n^wOz||QrJJ5 z0=d$>M-qMjAE_Th1$y!%lJuu`0@J-!V9a;38MqbfCz?J?lX1O+iND^tqpA%USu%J* zXn?q}QnTSA?)~_*k5;TOE{fwNi1ahhrN5|1`nxV22Il9a+sa&KA}cwD8h&y{IKy2! zs(2|}g9t6lxHI6?RxlCKYIHPSG3~(YSxN$d)YffFUEspww$*nIsxHwTx;mgi9GcW4kF}2XR%qiDQAtU->A7r^|Y6)!-2V|@>nj~(%0irxl%!t_2%YQX$kBC zm+@9GwR2s&rpggdkCrOdE-!($TV$l0%)Vx^*We%|v)5qUungoYuNN4FHeP-%2@^F= zl)=N|1rRecqPihrR@QXt{BNE~1;>80Vz!#wOychd+K6$U6On~V9Y&p~t0;aV7K>Mj z+t1?RFL9n2LH|OBl)9&F^sHSw(eD$g00l1TW4NeD(V8rN0ET%PDi2FqTSUNoIz7$4 zE4xW^?GVuud29)z_c$p}gFTj)Os}|K{;&$kF<8m}mRPpZwT_t;(rEr}-w;Hf#LrE` zz{CaqU}(f8*c=o@eN;6v_taeF(`Vvu*}GWSAIK;Xk3tC?xUpaH7Y+M_r^Zx>u;{*& z;hj_3>XqI>_X^hSD|FffKN{QLCH za^???*kn=W)uYH5V4Nxomd6WY(O-LAqO(&~w;2$ra;pe9oS+d-sG6wD@Je4>EpBwQ zqFZ)LuTC2AX>RxslfU*`QC>jDE^Pg>cQmF@UD0_yD)Icb{<)|$kb3?!{BorHq_Hd= z#w8KU9wkh@IvBxrkymP|A`Ca&bCor$)R=5SpOD zwKKcSz9QN4tkh=6oO|S^#pO7TF<_-B&vN7Qk2pQ%;3rxpo?fY;CQ~>J6$Fp9)IYe| z%M5PhL^!%CDnzThxHnsJMZ_ff;=7BQjScUPY@6)_8+xE#UklFHj%@>hWC(7Y;L&Up za^`9*N8wY&2h=p8ABbwgn*bf$mQ2WyDrN!K#8*8XQXXq~?E6ZDdn79paU7Yzuw+bCJeIxHhR!8_)<)t# zB!^21Y8I5YRB)bliDBmP);01{u{_?IHngAvb!3#Ti2j1{4!yB76TXQHV13NVoIE~> z2c85Mng6^BoX~s3Y4Dbn%Q|g$=hxQNW`zjjlQ-6UD=TN+70tqqPD;L{m`=K`ESN#1 za_;VnrZNw*DysBv`oll+;17IE66CKvznV?8v>6*%pySS3h!{s*m6U2!yBt17OW;8T zmSc;eeA#_A+}WK=l}Jzwse&H(Y#R2&ePYJeni}&WL37HodBZ)5Gq^>Qw;jR>dvGz4cdXr&7g)BK2$pnZII%_Xe z40%gtOu9xybnbYLjPug!vfE+PV_(rswP@?&{6s}WH+H<9eAL11hDI$6xJyEWr6*R@ z+Hvm9*V3n2q=;;QYQL|@muEGOsbqo=CtZP8mWLO(b_ccdIbJ_*=H$Y4+SFd>N13<2s5_vxa<3dO4&_e`!p?b9-OMyrr73naF-vDyXeX7)z)W zTrVhQIT+?=$mgbLG5t`%jw#NoY>W8oJx@ z#8VGTy@|9=Lzkk~B--j4G}Y5O=__Fr2a<5oT#BtTHjXz1j^Ok~)cMWJsojh2)Qd36 z$B)R~2G9mh@ucKt$0yesuX9(SeU8TIk-)4;d8~{41TuQYu(aPtTqZ03RQ+9*6>b)R zZd&?~_xqc2i=PVlZXP|g2Wfqg3B(noo9Zrd!n(zhR=Z~*QJAm7+dp>~Xsvuzsb2Lq z@?B#N>%u&7Mwv(9ZAj{){O}#GeLE&uI_Nm#B@SGW=A!ZZvi_GEYb6E4%g;0GTeZgr zFaotgtCGPQBJOgQ^G%Ev;jg7skIT2J#)qzHQ^hh;ZD?6<<_9}S8}l2!zP`-tdLPIn zWwL7*EDovoN|YtL1x+7vWlS93UVxs^GG{7D2-Eff+jD>#{J5lAx2iMoaZQtB;Wus; zn+_Ink+zZ+KVP9ml4&Za#K%Ge#tMb7_!UDt-i-1*Rr^Xaz61j5++yu&V8(dt> zICq}8ZEviX_OJ7Cc{iu51D>e%nk za9T;4oGDK_%yL%GQj{PI?+$%Zo3O6#C;HRPU|wD*+=@{&o8nRxDW|A#C~%`m1G8qM z2q`>Ul)53MPT_Jh^a&TVHS`dVPms@t`qsU-M_6ucT~6{sg>StzJ>J8`*u_Q4=yJY- zr!a{}Pd7%q+rr&?mSLB32RsxesH~3f3+JHO`b@A*`*jFYZZWuLEay>vVg*ZnzF8QS z__u__C|_~U0xz7pM$bxaOP8DNYcrCQ@`8D^NPUmYl%#erxyK@lnEC9li|N|(!d>H8 zx4hDF7Z$#kaS2}*PA`is4G?HrMCvxoIx;eSz4oKP3hQU7m=G3cn~~cYDFy z76DWz?Rd#oMcY!B6mOvs%@WTJn{@Fj0ty(ZXB}fibYjm-i{7x0m5B z2pbo0R@r7jOuWCdkCK-!O+J(0EX@(tql)8?7#E(|RRP=C3Hv`4b*LOFq+0l)qEhUl z!-Mqktt9zjPYCum2ko=s*poNqr{p}dE`!tFl)U;YTO$us%f_u!f4b;7tq09<(FesQ z(Y#{7o6>v4g0K>P4dJVHyrR>Hd!>lBE4NpKOJ9015SY%^H80NZe0t~2O|Px)rOkoW zaL16qe(Zjw6RV;c=to{TDo-_eYMCK_EJrgwlLA&4Y@Iap%+K#TAT+o*V!8}={v19* zH?e+I@lH2QZ+n{rNJ*)lo^v(_CaTQ8tw5YusBMfE6toOt{3uR`(y#T3_qFFae3Q2g zE@fHmvZdVsKJW0^Zx7~8QIRbv$+4YZO7z~6AK897Moz%jt_%Kgb29r$4JAz6^f~1Z zJZsm@PO~YGnuXC^L;jls%{tn2C1feBd=%H}=`+H^7Jj*+g%7r+(WK6yR^PhmqgFEc zoTgf!Hix++HCodX)^m>14Pqg%!b0nQFx`BNVl^rwAA0w9m<89yEvWhe^Mt7 z7mDd+h*C&2N^HwI5d!_nsWd=C-XD30w`Ay%TfLm~<<`z*d7Iyr=PhT|j1f9~fwr#7 z2Q7X=$KlF`ZS?JkT{b|A5o0V-tIAe!mbCF^PD+i$s?XC0y{UXE{Us7N53VG>G1o)Iy*JvIX~+pw zzl=sW8ntk8REMe8^*nm^Gl!#ZF7HvGiK=6cClOa|ibj;vl0Ls**uaqg0;;P!qjeGz z^^o0!!j)p&7yW`L>%ML* z2~r~b_X9B2T3#6QPB{E3teAG#jCkHQp;K3z#x$X+SUdjN0AK7)J8|lEYpgB)jUPr) zvv_FLv2SUnQf|1QSPn829mVRPHY+v3GO-N}t>spwe5vFxcqtk;Cz-4WS-jdy z?EG0Ky*N$OZ7SS85&kHKf$Epd;uXL+1c1$!%V9g_`i=hj=wfIl+^nv`c=AcCWXfwD zpi=DtE`eslnjjXj%zG+&X`$gsA?!Aj23yqgGc(N9XakSyFUE}6Y~`(EL34-Ou%=<- zFlT$`Ia(GERc5{pY<%p9^MQc2g^bPOjC_vv6xGJlP8Z0<)pa-f8Vq|;8?z+EBgNHAJ z7INS(sgFwHHS#;w$e8+`kQ6Z!rBvvf%Qzcbb|=R~l5*{w9lJX0rRglh1xS#n{tl1!7@h;Dw#r*``A2jc)S*GzFv$~_c-{{k z9=cmN6_5LPkBN7(`MY_UBGVqoKD62}o6%N1uut{H`NO)|PXT0uau45EG9^fcv8Lps zO}U@x_AXsny>xnB)rhM2ZF?o`rv6x?Hr#%s@ot+OH{xhqvE$_mxf{4*d$y zNE<64Q*ZyYM;;P~3U}6CwS|YR0eZcrXYf(2drdJ!1Vuo6apODREb)Y7kZH8 zwv6v^U4*iQEg4qlJZ08N`2rj3jMjf_M?=I%+tqS@-~XeX1Alpav7ILYvt|l64-8#x zuYKBHflJXV8~&yLs$GGwk7-(M?0jIC-;?SzPuoHuv4m z#(KGz*q>CXS94eOQ(ljdZfe1+WkhW3kaHNCbV{wQs+A@<>=}|EwXJ#k7e*#|eN?Fs zJ9|^Ac>O4X=X3C56chTcR1Zt!@bl@Dmi7VS3G5N`Cy9GMf#}uH_l?pxDc=jn*G3kD zj6_!5a0%&cPvAFan%6g(=DgK?-4bm&U#(YsyBUIRN)Qh=`N{DDDEDnw*bX;BRNcgR z`rvp>IUoaQMN4~yt&aU9PsvnF28q-Y=o2168t)z=UDXOsl}^TSyRt=iuU(Z^nWZY( zXh#J4^H8(vgwrI6AH&9-#8N0Ea#zP4-42h-f(X@9HGud{VM9Fhy|Q33kC{Tl*w5&C z-Uyt^tQ}X^&4bHd)ApZcv%aXs`$UJxdm_q0&_koeslIj85CHYG^;zqGNP8WqsPwRw ze)<-o3y;Y}71k#=GMKj?e~#Z=6U3*IBSB0)`lhbhstR#>N)oPvGgH!dEG<`Q7lbGg zW1caFWEFTvpN@>8^kTipH{;}?T9CRO3T`3qc>QX$bFM#^niOHnNXRg$HyJoClo zGReqV7~d?RvFRqeT5i`%c5!A!osBdiLpMA%;djZh2F|j3pTZPllv?7Wjj+U(?2%HD*gvw`@wWsmWTV&=87R3zW^vR{h`+Qf+ctXY&Mn0i7mZaMt*T=U%W9;Loc)3dkaR~ zL82_#=?UnR$Z?X|>u9Q)mg!epSnoWqmbR!dW0YTg!qdFR`YRR5IBXR~5#(HR?PaZR za{3Kky3l!q5G0B-B2dKNgcQp+bYS%qbF3-ggdja2H7djCd8^mL=}++Zd)tR+&ph9H zk`H>HVKxwfQq#*znc8O8;VeZ_qw$U45?KEfxWpz?^p-p@{;*nq=wmt+K+}+(d5d7fo?yd(yI5-3dZo%C(xCer3g1ftW2m}f4_DJr$xifd& z+iO+V?yvT@rD|9G=0N@t{0NlMmetQ6bD;of?Zvp^-QUL%whs+jLftP_Cu#T*y zq9TCtMg9W-2So_@WdZ7?X+aVFB^yD}1Acje0=&3|V*hmqz`Zm}DE{B_ukaut)&Q8_ zIsm}Cm;Tpf^3s0U`rV70+PyOc$bsF!5ONJL7yy9If%?D8AG!9hmX82~`-`PHD3m{5sca@Vrf&~edGQ~;UV+p?Nk*qeb_-EAFyu>b<@pcl~= z>|#ppZVRz<2DuAT{?-7!$iHA9CHZd^7aKuJ9Yqy#aeF5)IX5dCD;uQ{A~`v^fRlwK zNKHcOPx{M~Amtku7Y7g!=;r3e>c+up?_>pJ=i}o8vb_ere$Dct!Q$*;=VI#4V&_ct zFC+hNM*{3@?qu!YVr_3n{>!eZnZ2uvASLB5M}J*^#%b&Dmm@ppKeKwt59n^{0Ay!n z1O7LXi?!vy!}|y8*U0~*f!(eD2kqC$|D;)%{}rKws}tn+L|B*u!4R-5*v`fIg~ty3 zEB}}D1YTAGB<=(@b+LESu(yW@{jLT9;D5sZo(+LtVT07b&h`-3Uz4V3XYC^NS^)Up z;QwUl{F^4k$NeYg@8EwDbpIRS@8I7Ezvcy`V(kuw=tx+~8PxRON z{3=O^wa{PNQvmoU@^7oZ)PAG?G#~){uigY+P#0?#2>74AemfF{xcn3ScLcxo8VF)) zXC+AK&SC+!GTs-z=H4D?)wesQMKjsqNW#PDD(bv~ks+tHP?j;e`ZoG} z|J8X+@cL^8Iz<+dz;T=N)B2V0I^XpQ4LiP(D&Ze+(3A9!NH76VUTUypCGaI_%Y|`Y z<(Wibp#8*Nr!VLBM_>!cP4_b9i_%*VRY)kPBQ5q}(6j~4xZ2^R7E=>Um6E8`1l25~ zh=jo>VT1+m=&F^A$+IC-oJEpsGtRp_?B(NYG%7zV!E~A^o7)zsR&q{<+~jUiP((0% z2$S@88bm3ObN-YV%4it>_B;^e(HqY!^WnnOQ>H9ZTMDNYrs+zU5L_eUf7OkF9gVUy zkc!)j8kK789B}#`I`k%&cNX<*qLg);K2TN!PD@NxH30!F`?mgrTru=E43Hg3s@I+p z^V#;ROzh|)m>hDu%P#9KOb}1jVaRdVfKsZS!ts79+{wFR;3#!54Js~=$o&>$ep66h z*1%487HkFzaupjDzw+L5WBWQDwy`W0fvWpb*3Xw!0UyHt_y4ICAmY8sn=?~ zSk0*#Px@+65?^0x%hPwFh$?G%f}C0khR+=Ggl2ND_~Mzd109Kq&>S+cC+W)B zBadyDaFOux0*yEd>sv{{39Qity@iaH7`!csT;4i3y0Z#=qr^E(Xn` z?A3=R(`ztWqG0eWKrdN^FJiR3_;o#ML|%q*(da4KMkRVkt_)r>|B(WNBv z%6z2{Wnp#QuhVqBya?HPMfa^pVN_988PVo2fkxd*qQ)M+Vq$mV9>ZuUF*T+8DHcSEyv&2cpZv~head)v znfx?VG!zmoul(1Z_U4A>o(;S=s))+AW8Wm2Gh*~aI(_(DqdjG3Q%{V+Cvw%pq%Hd? z@-+ELp&G*Wv>whvK=uXH?p5D<3NhMzG`x5{JG<&ToaWK~*q8Xt-C@gFp#Ey&sOs?W z$3e{Y<#dzWFcvB{H|!yx-l=%CFn>agwEz$8t4K{ZApvEl%A3>IU`-wZA`x?dKOt{% z5BCpOU?pck1$P5Ahsqyb+HUJp9{JB+vllQ5T~N^;SW-oFA?vKA&=MMHPtt~c^_WoPt7TP*W~vUm-@1nGe{BaLO-4vu4d-?qgKb> zm=h>?x@oTv#{jn>(qB_cmnlruTKlCqzZFjbT-c*blm+6q?+F^A=3SYTjIvv2!`CvR zuabELD3mV1JLpR*rtmwJu({TuB7wjL3ha?Eix{U;bEt)5mi;r$F$&Yl_V&+H3DSW; zDs~lEh>+LqMdY`WiZ%=@rLjx_Mj8$-A!58pm&+tlJ5LYsS!WwmZt3@!+p{w>*^FOB zN}Bip>U%W9@zUJJk$A<}#+^6tjffz)V)PZ8dDoLA2Cp){5Rfjhwp0)zt%ede2PO%ulGp7)TkT3)sloUpvBM&!C2-I?pA&v+4 z;Qf5{7*yQCwpFr)`_q95s&?{8|NZ zKhuU)Lzl7fhNVQ4y*A@*RRYmB)e)IKIORw8>c)P7?y%0)t0-$S(Kyce!!uJFtS(*G z*#4!w>Wzz@&pRhiE8rYYwysdoAPHipWBhrbtg4Sg3PLjBWI!uw8}2@V{3n&IY^?^t z!bvz0KOIZeau>Z3Gtuw`l#S)q`+X`wtl9(ux3=D@WQBfIZXs;1Lw>rQ0-CR%zju@q zp(YW(k)mpNqq89C-}U;!ye^a2;w-V;A!MNd?M%T7j^*^)-ZYi3 z%CUH(jV4ZJJ9j}fj;W@?A2Zkt`TI`)cRX%Tt0&nPv!$cm?+HBJjuZIhqExQD%gMx$ zTNmv|D^J3Ank1#xKLn=Mre~)^JV4G~cSw!dPjlbPJdIhQ+EI-$Uj=!aQLt)Q{;+!` z7t2mlYI0RF!S?ooP(2yD&r)t)7V++*k+$n}+IB)0v|Y`@gbGpy!$6RDXLB&Yjob3A zIvWkqQj3i~h)A#Xv-Gms8fQ6qepzW?-Uf^V#3ATq?}Mt*lN(Xf$*wo#nV4qG5_Jm} zTm~B#;@RTW>8qcA9vsCW!FD3T;ZEf?ZPZONEF=oGlx@-NDpDss4^UAM^KerkJom_K zmdwzgdDnn_v1;T#Y3*&Ca!g{znBgHMm>pW z+pixwTBnSYa`%2`sh{5fptM_?1 zmRueeqNFlNgFM@xyBZ9+Y#X!m+1^S7s6EF#_}RpKH4KO8pQYcg`{3{no&S)VM@2DR zlZq!u49T}60@V#MogZdjsCa6mi=r^Y^QS}4Ae&Y=JTUv={2bA7pMw3Mqu~#t)Ah$M z#P@?L^f!~0ecC)^YnoWrkryVWqzTyrjWx{vo8^^jMy(H;^_8vP${A?uGL;1AX1TlV_CPHT;BaH*Bw}B)P6|@SmTK z*kL9_?fv3*{~$SCJ9xV(1Zue-Yqg(kS-s@+*;(qFnkG4au z=(&9oDcvk6G0Q&XBUxSl{Q0$&HQi~pbUT{TSz%S#N9A3AZ3cS8SNspSBwU&#b1F$A zQ@1pUn5kqs=xT9R5zX3eDN-m%Cb=FbTe5B}YDA|oSDcUgtB==)oYtWjn?LXKtQT#w zz7P#(;EfC!`uv!oKtv^(TbO$+*Oq&_StG4La7n=E1f7D29rIg<{bl)&HWn)r&w{OK zjgGpv*4QM=Z)Ed-8tfU=n1noZ^d>kS58{#(M9)4(_hKn$r1~$bSt*Z7`JlXdsxib5 zoafOHx>!Orpr+k!fjbKoqbD~c&IylbcS@z%Z96x?!QY5Z&jU5NX>r75P2tj_93l^07}TOwT(=5AJLFg?*rSTUj3U^bX|`W`37<=V8f#aeW2 z(#-L0K9TE-zClJDX0`yM>y!iYzOr}SabZ1t8R7D<4OI@Z6_J6v+(4qV(uoFE(V@T= zI;@mKpA3V$Q7t<60R`J`SrmGvVIEI-?@h>VWIH4|K>}!7dAoE`5hp92<@YJ;i(6Zr zx4g%B<8Dm%qZzk{L9cW9weNGxN2*arakys(XVvM>u>`wP8D>aCSv*0_I=2EspT3%9 z^&dFwR!~5_oF*(zx3188*31F8(i?~`5^gY`G3|HbuhpL7y3^AB;EV_e|j@ zdpc(2HV3-KlTNp?;!8}S1Isk^Ol!;c!^1^}?IrvRzR*`0hd8WnO_j6;nWLZh@f9HzT9LA~EZ-dSPIz+>c_c*#${J6KoT`vM>?{BUq}I1|)g)eFvlO}XK1kW4GH6(Y4Zt$1{XU*}5~v2oRq;{42o?~E?rUn=u-aki@>I0BUi%Pz-aaCE zobZwK#zA^DV*O)AHnDOgTE9Qh(drlPg0R)C1m0RI;$F616eS5_s(T96X89qsL7ewH z0jAP}BBBWz*ctpQEv=qAMh4GzAop*Xg9PH^F(jIw+`iUq$1y2q-h!224>wFC_0&?2 zDPvO@(PviJ6WYXc!i}XBv!FL~;e>-C%SOz&o|ck~Oz~cu5F$Kx-0$o@nhs015*2?h zswvIK)6uo_s=Tq`aI)idcE@!!cY;_t`6w}R4{lK>1@?6jr!_^Bwsuj~;vbY}L0T^3 zJsqG2Q}Gcim$bAe7Z|iL`i?~z(b^0@6d%cyi?a341xFNAjT1>XcGDx|bdw18ikern z?5Xb~?hRLTB0)^Oj{{j>1Fuk`g&WqucnXH^ zwM+~0LwW|4!`01_TB5t^ZrNz^D0jh84X7$5b@G}nstBBJa$!b9vaS;z+y{EdW)ZI26H#~6N>Iy7x!c%#+)|DYYSVBk|9U!hSy=iR5S<=cnP{I zV>Zd;DzEkevBJ&L0*nmi>?_|i-~9lV>9^F4SB}rN5;77lS>YA<9*=mne7r{!daaIS zbaSffjL31CUB({+DN?zP2AQ#^nWj<_z%hD}6C!@}5De}(gOh%+_832$PL_dl&qR?g zUtVJ@9hHj$jwo^C32KgJJ=gBA)vdWGL5*qQjg6618t!H9YYAkhf$R!^=T!0Ao!cF*1I=ogK1`Fn=-m%y(!&^!FA_Sr`>qn;lkv>@*xa!r2ME;c_&R_UmGhQ`IYTi zh7<)I4fHJKyj-2>UZe7y8?|0)oW5;R3UJ5T_)aHed?|TJl9KIk-Dn9ex)n&tGEDWj zPO7!{brQ4-8VJR-V(eScLC@36@@Ky-JjJa|)J6I_92py#69;@hmO%P5iY3WtGFrdE z-5Y8gs*&t1v?913W5oa-Y!BgES)6ve`&J=v;1rKddf?yse%P47|6*xsiHApPs|s;zgFM2Up_t*}kF^k{1j2s$n73s?iQtRqr+lgATNX}9WjDfC}OKwgCrCji1 zoVr(I88;yT0}iVq1fEoHSt4ln>7%b}TS`d|c;3h={gI?e()^r+S#dzXaJta>QOI@% zC8BsFEiw4uydY>IYFF9tYJso~UXV@wTckuM{!T;B))3}w4;phv2@YRpsy(nuFI$2B z$V};EDU^+7{iLP4>jxjzMHVoCY<+TRG9ru37-33YauaeNzx|H4T$0KnLu zC^c!rlg-6-aC74*tM2s;MEFBE{G;(@kKj~6!O8lZ)`evLM@YFS8Y@2D2>z&h6t+Oh zPlDpSiOJ^EK3qZPg6{0#v-2J@xzQ&zKC@qX-OD(Xo-4PO;8+Cbb)yLOwecS$_7A0bQP z_N26Js(Xf+a!tO_okZ9Q7nAHcdPpyLh5T8GJ^BmO$JgVX<>|}muF$U)I;rj4qlRU= z_RAGe`a>R3YtXHng4z%_gzPRnV|>D0S?C0RRzFVzb3fAsO0V#tQ8V6v+kIc%SM)5OtZ=dTiXUE7Y#y-4G)eBQk(6#X6>`R7( zq@ze1Xv6ZFu*k#glbvOm=M8m`{sve9NawUo`U&TY{2>s<7DU{Z=Fgx81%(7o9*mj4jE? z>urH2>eK z5c+tVHD!Z~Bj~bi9dYc1bt_X0$AmcJ>Jpu(YFjS$Sds>Ii8D@TI9Mr;+8pg`1*W|} zj{i|F-ICmHbT=8@a&~t$7fsqG{g@zMRDEm8ggPhbjzO`0xVSrn@g3>&Smm0p_gSqb znz7U4?ez1>UBisTVFv@Q;S?R&S!bbxBKqCFkAJ7fj*KglhZv*1j+_>}BpboY=cg6> zRufCs)Vrp-rxsybkP2Q{M7O!A5Be)?{ti?WaVXn+w8p_@wmvBQFt8l*w)O{MhRVC? zp{(I5e%GCrvog5%WYZCiW~wl%d&H#hw&%f$3?USz)`$?0f*}oFEG{y1XgR?H_j1i{ z11V&$!#1SH0iB;@v3-+bmMykGlT)(gdcI+bE9b~>*!L;V+~2-w^Yj3Zdvo6X=WVh2 z=j(ZeO?4_3O|hWq;A1@;R}Y`b!4oqNaa-Ou7ZOzijwDE;@7_NANZ^=H<~ppj%U@4o zQxc)ZFR>>X$I1%V*cQ^Nj5gYgncKrPlEjaRe2s4VLSHY_YaW33)2R52Q1~W6jQy%6)FN(WPc;>g+cgJITodCOldG zM0bv-4+s&;W8H>=H{25-n*6abM(s^yW8-H-WgqT*wuA5TNhqs+9Nj z0fBN!3dEo0^TbQdK4`326IchbLZ*PJT!cg1KW!ws%4_|!0!F%y&64J&KPdHY40Ns3 z;XUnSmU>WKQi;_R>;}!~Ld9^_$~tbsyD`ii^FAC`*?rk8X9(5Xe&C zlNvoqFnDdm5mST-e_%Ppb*~^18M%efA?~{xIot|e^*(Wb)=n3Axe@v< z5I1m%I(}!xnh(vG3rm|@xnk)#Lc75nxJ{r?9rC!}h2>24#g3H$e{Q>+E~=XkRlN^p zBvJ+YEFZeB7)Fw&vZP8+hm$+QXZ*A~3Z6(0GX&66c3x9U>ha$YONc3nG0zXDepJO7i) z%)S~p+2kU!evq3wFAk5dLJ|0>>R~jawLxo-=M<;UGWp46C|>={a(CMGlAR2v-WJPA z&CzWp9|{dV;%w?#vwmU+i6dAaL#M&9h>4gZud+Gc!d@_4&}$QBW@S!uefhNKpjVyF zf|~Bb8@|RsO;;UfGgF!i5}dCGBJT?`9KR7_K?o(08a_LAE@wZ#a=T>9d9vTCJz&QB z;=Wd0b>La9Pv&XUA5lJ}#5`aiV6~DtxVXSGLhtZo);H63k#z8_F_~e#+GpME(Yn#g zoYoR|ENQ&0_PpU)hh^7F68;_rA2&+rL256MjJ#N7SBFoBqK^@gPXIxvrR;S87ejn`9`pSE4fCa_gjzi9~s@S&TG_fl@o$LTg%BJ3qIvLR~qmJ zx1qr3QLd6f1pL}-F>^My69h=(!IIV=W?CpjQRGaF$HQk83-)d%W5@|5Ak8(3Q6!fvSvi1oS-wXq_yMA){-3;3nu&)}o>t?HLrfzQASPfCs>YOxMHdfF zdaX?;{qe9eQI5Y+hC~x58yt3l2&ez`*g)rxA_r6VZu5ihc;M|cSUZ?xtp>=_vO`HO z2`7akxrn}T`s9r${frrdhA3fug@B)zhli_fcU-j9u>mJ5zw4&Ku6A(v^&HUfX)QZ& z6j!}K&6>O_gfPNFmRTs(>wFoLI*FG#iWXY&o;*#m+N|b6T6DApew=AQ*8Au@Fh%f$ zU5i(Xpw}sagTXR0bt9Kb>f4GKS2*}HlSQhDMqPd_p8V6fu)derSG}`&NO|93d6`W& zvL*$aMq5B;mhM(;Y6J_4`eF1Cvss6L#SUmCYA1VQVvI(Gk$M=eUKM_r@>3;WIl4kF z_$(EJfa)_VygHV6Q(?sH5?k+EgR%`u8p853XK@93zKXrxdqURR#bujD&HU}GOPuvK zPq3?ZP%5T9{+l+;U=X5X3W(r8PbJ8ZO`(3dXtFPGCMii>jll$1V1G%vF>lk^Ow;6UJ?^}fg8<2)$H+gfKfn>JX zm*1 z>;x#x((c1MJ6t&THcQ`?hUAoUk+SLvZX%S%plyzAE4vIy#(-wH!t}b$skrm1R>B&x zxRI@P409a^rZ5VqOnrYK=~n0*Lj(`cquT1)syWvbp>FF0J7 z(Qd3Xm@0R#&yn=lhh=icttAJPV$oV`p!t1btSnkzJtF`(WuP8?&@9$^NL8>Xn1_NN zH}T?P=QkV!+(=Ml-pMk+Z%EPmWDhdaSsv8|)*86*F>-TeQ^N6_nyv`!du*Qv>U>tp zj1{6y@KGga;d|KX_vC}b=ov?+3S@bUTZV_ykUhwz7-cZv49A|_LG}(R>8QIgJg5$v zAP%)#*!PW10K*;ovWNZE{XjCvOoHHIE+Sb_)}+5JJ*N$%{Mp9sHN~i1Nc7 z3p5wK8$aX{rDOa+@~UJeS$Q;4v14AA>|_GXWW6-_mg|B*yQh-~ad$(4s_`=v&FygZ zeJ5;>p0B*aG$Z@7qhfLbTio-TIMj>2{$UiYl?oBhxbbY&Of3>jG!i4IYm<9eeC1{Y zA5S!VIte`$XtbMqg53Za74`f!j+H$hF}ZSO?IDAvpWDo5Oh+T;g?=cr+on)=eqm1& zJK|Q;qL9)rV$r3Hun4lQMEj78;kHapQK&l4`EszD-UW70IB8c28A-3^ajOMjV-3YW zZT;LDFk}k!9ia{;CI3`{P?i^(v?av3?K@1~Bs#fL%JA9tGwgnSlDlSa0pmusyYyR; zHVNzDcyOT{Wj`KC+zydwkZyNXyjaNGM&}y=9(k{LUFxoNuC7VeBf0!5ab^YrF<$p& zgjdM;&8YE;{D?6DcvXsnP7T^EV_z4I_*}!mC49Yi!HOdYEy`O_bD!oBKk(0JSlE|L zD6o%Vhw6^=qI^a^==?@27LU|GM2!`ZA_a~c6Qj-8NTfG`SE-LuGt4jv8N}{xwh$jm zE#n_k$EF@eRJ8P{m*;T^CoW7$ESsFBMHpNnE9k?N>C9i9fEM3(_P!wqdRBxEZ4BN?LvLv#eZ7vg2bHB+8 zn8E2h=NU~8r}#jHIlG0bzJkkt4`(9hB7#B^guxVOfoqaCT+=xJRhQN!zm zHkQ(1BVRCALpi#1j}K4$PG-n)Fu^S^)-}whRRd0TMOPM|35^*-L<{M~xRCK_rYDaw zQ%gK^v&B@zT@VAQeS>FGktzQfK{p$}8qnkBrWD3u;m0>+6o+B0dUdkNe@Y=+Q=r8M zVE=>Z>jL9zc^8H#z-Mf07~DdRW90Z5MtpzC;7J=3WJd}X28~`c{13kGkyq1U$Jt|f zAQ7<^Q>@K_goiqbfJEmQLeJz&O4wYSTFZHNCjDplP)7^SN;n3LUw_-lN-9ZIikSrb EAEFuSfB*mh literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-marketing-1024.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-marketing-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..f3e00fc4e27ec97143fdc9f07e633613278aefcf GIT binary patch literal 618576 zcmaI71yEeg)-F7QPw*iG2{tf5f?IGOWCjlq+ylWixVr>`3-gCZt?tgCG`gc`VuU@s}>F!;7clTNyqoJlq_=x%u001CFz~wXn0IUZR3xJ35 zP#p!e{d}l^Z#5NV0ac?k+Yb$ID?P*;RaF4n1C0m30#XC8{}Fj8lEA0`p%sA70GR*c zKiG+~{g=^yWYiwYKabf%`KSHw8Z#gGKhh7pe2o7sjRnZZ{6F-AG=P|xZ~vhJxxn?` z0surk{}kW{cA}>M07iwaww}A5s*31qXGbn`OJ@r!E^kMdf3yJN-l7krqm{ckgSVrD z(_2w*3C4d(h(6H&u(=r-{w3mWFTtp%s=)wrcC%s-Q3 z)E`E~4DrUS3{YUi@6nZr0qqA|fK(Jbc`Ie4GywoNs-c+|9i?o!&D2+sOa6 zBWLyYwVSPryREYm!#{S-Eu1~vB^Vk1arB?(-#9H_|HqGuhnvH{yjZ^GwsNp?v~qHP z%gxKh%l-e-d+6|dpNi=N@|$Dw%0UwXW(QI{Qm>?-**26?Ei)! z{xFcDuz!z@wzIQ?B+T5&(_EbU|7QPBxBuc z1OTJ~2ss&TZ(v0ZVJ39Y(Ng5f`)+E{>1&#Pl>|S4C?T$*igaU7;oAyNcb=jpc@)#_ z`PltUbfCX+sl!w6?MQr;p*I?z)q*e^O>C2S@Ipm_?{1l6G=xo2s^HHo6`sf(GW-0w zYv^`E|0_IP#^HKqc@|&a)OBT{Hsirs%9hTMAf!MKr~bb3+N29{6!qf;{;`5xH`p(J zC9(9PkZaK-+r5)2wRg$j5Ay1)5kX0^gx!P{DLZG8x)my%itDTR((?zZ8yecI({HjS zs>^rR%afdvYA?ThgdpS^MSR=w0&%E_ zKX}=d3xAqAle`V;3(1A$C8>^MfVR>;sD5B z8)i>1Wjilg_-p z-57PpEQiqYg`}w@Lc^UDt1`9&NL!Dxrxt3Kv`mRVt=bhUHnk_IjBRA_Nr)|-?Bt#Y zP`5JQ*!yiiFQ-e5U4M5D>U=|C=5<;0(zt~&awqh9WOi)6A8WL@l61_{HcR%xzOCFv zFei@n6j;P`U~U{w?VfmlvvjZ9W^L6pdu=lPl20PAb6l>N5_|8LTtHQ}NS-7$O{ATY zlUmS)iw3G#TbfAM#hHMhC^OxlQNZECdb{-c5#Q^kI?vzg;C-WZfqLWmR9L;!5nk&f+CYl_$H9bX42 zHt~^h6$qT6n7^pSwMo}Fn`5?=>;U-4>vRxQVN052^M$Utvj1WwkXHP2nBAzC&laE( z8SCp&uM!1~{DDTeew(Lz%XQja+&r`8U9Qen++AVaX+qa`#+y~Vg^S_$^ESkh>$@;+ zZSXbUy6ZH_)@@eE6?=`cYaH8d!Qs|bTM*_Rb;)lt)Ac`1hxf0J@h3|typ24LcFB

k>g;7$PlNiV}_kbQM6y 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�{73d;y|!IUsO=wq8jD$+%0> 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

c!A^rb{axrn?|0_#iNrt{qI)Q*&<07v{(wd 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

Va}9*rO6n@K2aOX7;58j1Bo~vkbf;AP*O*h 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ℑ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

Exo>4qK}l!KClF-8C~9$=l@jE~z|a%TXtNHXY< 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�g{9}^Pj+IT)ymb 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

)P@yhu8I(f7DPD+D{- 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 Kt4hQ9%KreYur8bc literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-108@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-108@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..508a5293bb57db8c178c1ebc50c5b469f72576b8 GIT binary patch literal 34858 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 EAEFuSfB*mh literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-86@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-86@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf5b63cca38ed8ad169cc99c92bfd4b83592401 GIT binary patch literal 23984 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�aKu$-7*bO*CGO}oWKh-RdW-YCgsLS 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 literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-settings-29@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-settings-29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6a4c2d88d626ddcda7bdb7032a9f63d459973772 GIT binary patch literal 5300 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 MuV(@))o~8_9}6SmPXGV_ literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-settings-29@3x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-settings-29@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..64fcb30d1e1cd2728c3479296431578efce98376 GIT binary patch literal 8512 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 NE6YETE0Hk|`aeU$Dx&}Z literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index fb5454e222..eae9d600e1 100644 --- a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,9 +1,211 @@ { "images": [ + { + "filename": "AppIcon-iPhone-20@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "20x20" + }, + { + "filename": "AppIcon-iPhone-20@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "20x20" + }, + { + "filename": "AppIcon-iPhone-29@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-iPhone-29@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "29x29" + }, + { + "filename": "AppIcon-iPhone-40@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "40x40" + }, + { + "filename": "AppIcon-iPhone-40@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "40x40" + }, + { + "filename": "AppIcon-iPhone-60@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "60x60" + }, + { + "filename": "AppIcon-iPhone-60@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "60x60" + }, + { + "filename": "AppIcon-iPad-20.png", + "idiom": "ipad", + "scale": "1x", + "size": "20x20" + }, + { + "filename": "AppIcon-iPad-20@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "20x20" + }, + { + "filename": "AppIcon-iPad-29.png", + "idiom": "ipad", + "scale": "1x", + "size": "29x29" + }, + { + "filename": "AppIcon-iPad-29@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-iPad-40.png", + "idiom": "ipad", + "scale": "1x", + "size": "40x40" + }, + { + "filename": "AppIcon-iPad-40@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "40x40" + }, + { + "filename": "AppIcon-iPad-76.png", + "idiom": "ipad", + "scale": "1x", + "size": "76x76" + }, + { + "filename": "AppIcon-iPad-76@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "76x76" + }, + { + "filename": "AppIcon-iPad-83.5@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "83.5x83.5" + }, { "filename": "AppIcon-iOS-1024.png", - "idiom": "universal", - "platform": "ios", + "idiom": "ios-marketing", + "scale": "1x", + "size": "1024x1024" + }, + { + "filename": "AppIcon-watch-notification-24@2x.png", + "idiom": "watch", + "role": "notificationCenter", + "scale": "2x", + "size": "24x24", + "subtype": "38mm" + }, + { + "filename": "AppIcon-watch-notification-27.5@2x.png", + "idiom": "watch", + "role": "notificationCenter", + "scale": "2x", + "size": "27.5x27.5", + "subtype": "42mm" + }, + { + "filename": "AppIcon-watch-settings-29@2x.png", + "idiom": "watch", + "role": "companionSettings", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-watch-settings-29@3x.png", + "idiom": "watch", + "role": "companionSettings", + "scale": "3x", + "size": "29x29" + }, + { + "filename": "AppIcon-watch-launcher-40@2x.png", + "idiom": "watch", + "role": "appLauncher", + "scale": "2x", + "size": "40x40", + "subtype": "38mm" + }, + { + "filename": "AppIcon-watch-launcher-44@2x.png", + "idiom": "watch", + "role": "appLauncher", + "scale": "2x", + "size": "44x44", + "subtype": "40mm" + }, + { + "filename": "AppIcon-watch-launcher-50@2x.png", + "idiom": "watch", + "role": "appLauncher", + "scale": "2x", + "size": "50x50", + "subtype": "44mm" + }, + { + "filename": "AppIcon-watch-longlook-44x44@2x.png", + "idiom": "watch", + "role": "longLook", + "scale": "2x", + "size": "44x44", + "subtype": "42mm" + }, + { + "filename": "AppIcon-watch-longlook-50x50@2x.png", + "idiom": "watch", + "role": "longLook", + "scale": "2x", + "size": "50x50", + "subtype": "44mm" + }, + { + "filename": "AppIcon-watch-quicklook-86@2x.png", + "idiom": "watch", + "role": "quickLook", + "scale": "2x", + "size": "86x86", + "subtype": "38mm" + }, + { + "filename": "AppIcon-watch-quicklook-98@2x.png", + "idiom": "watch", + "role": "quickLook", + "scale": "2x", + "size": "98x98", + "subtype": "42mm" + }, + { + "filename": "AppIcon-watch-quicklook-108@2x.png", + "idiom": "watch", + "role": "quickLook", + "scale": "2x", + "size": "108x108", + "subtype": "44mm" + }, + { + "filename": "AppIcon-watch-marketing-1024.png", + "idiom": "watch-marketing", + "scale": "1x", "size": "1024x1024" }, { diff --git a/apps/swift/Resources/Info-iOS.plist b/apps/swift/Resources/Info-iOS.plist index 0bfa2b7b2b..3bdf4210c9 100644 --- a/apps/swift/Resources/Info-iOS.plist +++ b/apps/swift/Resources/Info-iOS.plist @@ -8,6 +8,8 @@ PackRat Swift CFBundleExecutable $(EXECUTABLE_NAME) + CFBundleIconName + AppIcon CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -17,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.28 + 2.0.29 CFBundleURLTypes diff --git a/apps/swift/Resources/Info-macOS.plist b/apps/swift/Resources/Info-macOS.plist index 6545646a29..ec5527b16b 100644 --- a/apps/swift/Resources/Info-macOS.plist +++ b/apps/swift/Resources/Info-macOS.plist @@ -8,6 +8,8 @@ PackRat Swift CFBundleExecutable $(EXECUTABLE_NAME) + CFBundleIconName + AppIcon CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -17,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.28 + 2.0.29 CFBundleVersion 2026071601 NSAppTransportSecurity diff --git a/apps/swift/Resources/Info-watchOS.plist b/apps/swift/Resources/Info-watchOS.plist index 9af12ebb0d..6943a9d00c 100644 --- a/apps/swift/Resources/Info-watchOS.plist +++ b/apps/swift/Resources/Info-watchOS.plist @@ -8,6 +8,26 @@ PackRat Swift CFBundleExecutable $(EXECUTABLE_NAME) + CFBundleIconFiles + + AppIcon-watch-notification-24@2x.png + AppIcon-watch-notification-27.5@2x.png + AppIcon-watch-settings-29@2x.png + AppIcon-watch-settings-29@3x.png + AppIcon-watch-launcher-40@2x.png + AppIcon-watch-launcher-44@2x.png + AppIcon-watch-launcher-50@2x.png + AppIcon-watch-quicklook-44@2x.png + AppIcon-watch-quicklook-50@2x.png + AppIcon-watch-quicklook-86@2x.png + AppIcon-watch-quicklook-98@2x.png + AppIcon-watch-quicklook-108@2x.png + AppIcon-watch-longlook-44x44@2x.png + AppIcon-watch-longlook-50x50@2x.png + AppIcon-watch-marketing-1024.png + + CFBundleIconName + AppIcon CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -17,7 +37,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.28 + 2.0.29 CFBundleVersion 2026071601 ITSAppUsesNonExemptEncryption diff --git a/apps/swift/project.yml b/apps/swift/project.yml index 90226b8732..344dd24e47 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -59,8 +59,8 @@ targets: sources: - Sources/PackRat - Sources/PackRatShared - resources: - - Resources/Assets.xcassets + - path: Resources/Assets.xcassets + buildPhase: resources entitlements: path: Resources/PackRat-iOS.entitlements properties: {} @@ -68,7 +68,8 @@ targets: path: Resources/Info-iOS.plist properties: CFBundleDisplayName: PackRat Swift - CFBundleShortVersionString: "2.0.28" + CFBundleIconName: AppIcon + CFBundleShortVersionString: "2.0.29" CFBundleVersion: "2026071601" LSRequiresIPhoneOS: true UILaunchScreen: @@ -130,13 +131,14 @@ targets: settings: base: SWIFT_VERSION: "5.9" - MARKETING_VERSION: "2.0.28" + MARKETING_VERSION: "2.0.29" CURRENT_PROJECT_VERSION: "2026071601" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat PRODUCT_MODULE_NAME: PackRat TARGETED_DEVICE_FAMILY: "1,2" + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon PackRat-Watch: type: application @@ -145,11 +147,56 @@ targets: sources: - Sources/PackRatWatch - Sources/PackRatShared + - path: Resources/Assets.xcassets + buildPhase: resources + postbuildScripts: + - name: Copy Watch App Store Icons + basedOnDependencyAnalysis: false + script: | + set -euo pipefail + ICON_DIR="${SRCROOT}/Resources/Assets.xcassets/AppIcon.appiconset" + APP_DIR="${TARGET_BUILD_DIR}/${CONTENTS_FOLDER_PATH}" + for icon in \ + AppIcon-watch-notification-24@2x.png \ + AppIcon-watch-notification-27.5@2x.png \ + AppIcon-watch-settings-29@2x.png \ + AppIcon-watch-settings-29@3x.png \ + AppIcon-watch-launcher-40@2x.png \ + AppIcon-watch-launcher-44@2x.png \ + AppIcon-watch-launcher-50@2x.png \ + AppIcon-watch-quicklook-44@2x.png \ + AppIcon-watch-quicklook-50@2x.png \ + AppIcon-watch-quicklook-86@2x.png \ + AppIcon-watch-quicklook-98@2x.png \ + AppIcon-watch-quicklook-108@2x.png \ + AppIcon-watch-longlook-44x44@2x.png \ + AppIcon-watch-longlook-50x50@2x.png \ + AppIcon-watch-marketing-1024.png + do + cp "${ICON_DIR}/${icon}" "${APP_DIR}/${icon}" + done info: path: Resources/Info-watchOS.plist properties: CFBundleDisplayName: PackRat Swift - CFBundleShortVersionString: "2.0.28" + CFBundleIconName: AppIcon + CFBundleIconFiles: + - AppIcon-watch-notification-24@2x.png + - AppIcon-watch-notification-27.5@2x.png + - AppIcon-watch-settings-29@2x.png + - AppIcon-watch-settings-29@3x.png + - AppIcon-watch-launcher-40@2x.png + - AppIcon-watch-launcher-44@2x.png + - AppIcon-watch-launcher-50@2x.png + - AppIcon-watch-quicklook-44@2x.png + - AppIcon-watch-quicklook-50@2x.png + - AppIcon-watch-quicklook-86@2x.png + - AppIcon-watch-quicklook-98@2x.png + - AppIcon-watch-quicklook-108@2x.png + - AppIcon-watch-longlook-44x44@2x.png + - AppIcon-watch-longlook-50x50@2x.png + - AppIcon-watch-marketing-1024.png + CFBundleShortVersionString: "2.0.29" CFBundleVersion: "2026071601" WKApplication: true WKCompanionAppBundleIdentifier: com.andrewbierman.packrat @@ -157,12 +204,13 @@ targets: settings: base: SWIFT_VERSION: "5.9" - MARKETING_VERSION: "2.0.28" + MARKETING_VERSION: "2.0.29" CURRENT_PROJECT_VERSION: "2026071601" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.watchkitapp PRODUCT_MODULE_NAME: PackRatWatch + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon PackRat-macOS: type: application @@ -171,8 +219,8 @@ targets: sources: - Sources/PackRat - Sources/PackRatShared - resources: - - Resources/Assets.xcassets + - path: Resources/Assets.xcassets + buildPhase: resources entitlements: path: Resources/PackRat-macOS.entitlements properties: @@ -182,7 +230,8 @@ targets: path: Resources/Info-macOS.plist properties: CFBundleDisplayName: PackRat Swift - CFBundleShortVersionString: "2.0.28" + CFBundleIconName: AppIcon + CFBundleShortVersionString: "2.0.29" CFBundleVersion: "2026071601" NSPrincipalClass: NSApplication NSHighResolutionCapable: true @@ -212,7 +261,7 @@ targets: settings: base: SWIFT_VERSION: "5.9" - MARKETING_VERSION: "2.0.28" + MARKETING_VERSION: "2.0.29" CURRENT_PROJECT_VERSION: "2026071601" CODE_SIGN_STYLE: Manual DEVELOPMENT_TEAM: "" @@ -221,6 +270,7 @@ targets: PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.mac # Match iOS target so @testable import PackRat resolves on both platforms. PRODUCT_MODULE_NAME: PackRat + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon PackRatTests: type: bundle.unit-test diff --git a/apps/swift/scripts/__tests__/app-store-assets.test.ts b/apps/swift/scripts/__tests__/app-store-assets.test.ts index 4a019fc6f3..479fb19a69 100644 --- a/apps/swift/scripts/__tests__/app-store-assets.test.ts +++ b/apps/swift/scripts/__tests__/app-store-assets.test.ts @@ -84,4 +84,32 @@ describe('validateAppIconSet', () => { 'AppIcon-iOS-1024.png has an alpha channel; App Store app icons must be flattened.', ]); }); + + it('accepts fractional Apple icon point sizes', () => { + const dir = createIconSet([ + { filename: 'AppIcon-iPad-83.5@2x.png', idiom: 'ipad', scale: '2x', size: '83.5x83.5' }, + { + filename: 'AppIcon-watch-notification-27.5@2x.png', + idiom: 'watch', + scale: '2x', + size: '27.5x27.5', + }, + ]); + writePlaceholder(dir, 'AppIcon-iPad-83.5@2x.png'); + writePlaceholder(dir, 'AppIcon-watch-notification-27.5@2x.png'); + + expect( + validateAppIconSet( + dir, + inspector({ + 'AppIcon-iPad-83.5@2x.png': { width: 167, height: 167, hasAlpha: false }, + 'AppIcon-watch-notification-27.5@2x.png': { + width: 55, + height: 55, + hasAlpha: false, + }, + }), + ), + ).toEqual([]); + }); }); diff --git a/apps/swift/scripts/lib/app-store-assets.ts b/apps/swift/scripts/lib/app-store-assets.ts index ae4399f925..6c42c224be 100644 --- a/apps/swift/scripts/lib/app-store-assets.ts +++ b/apps/swift/scripts/lib/app-store-assets.ts @@ -39,7 +39,7 @@ export type ImageInspector = (path: string) => ImageInfo; const PIXEL_WIDTH_RE = /pixelWidth:\s*([0-9.]+)/; const PIXEL_HEIGHT_RE = /pixelHeight:\s*([0-9.]+)/; const HAS_ALPHA_RE = /hasAlpha:\s*(yes|no)/; -const ICON_SIZE_RE = /^(\d+)x(\d+)$/; +const ICON_SIZE_RE = /^(\d+(?:\.\d+)?)x(\d+(?:\.\d+)?)$/; const ICON_SCALE_RE = /^(\d+)x$/; export function parseSipsImageInfo(output: string): ImageInfo { From bb2975cce203c33c17975f011752a4cf4547433d Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 07:03:21 -0600 Subject: [PATCH 09/81] =?UTF-8?q?=F0=9F=A7=AA=20Stabilize=20Swift=20E2E=20?= =?UTF-8?q?API=20fixtures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api/scripts/e2e-local-start.ts | 50 ++++- packages/api/src/__tests__/auth-cors.test.ts | 36 ++++ .../api/src/__tests__/e2e-worker-env.test.ts | 106 +++++++++++ packages/api/src/e2e-worker.ts | 43 ++++- packages/api/src/index.ts | 43 +++-- .../routes/__tests__/chat-e2e-stub.test.ts | 99 ++++++++++ .../routes/__tests__/weather-e2e-stub.test.ts | 102 +++++++++++ packages/api/src/routes/catalog/index.ts | 94 ++++++++++ packages/api/src/routes/chat.ts | 2 +- packages/api/src/routes/guides/index.ts | 115 +++++++++++- packages/api/src/routes/seasonSuggestions.ts | 88 ++++++++- packages/api/src/routes/weather.ts | 173 ++++++++++++++++++ .../__tests__/embeddingService.test.ts | 14 ++ packages/api/src/services/embeddingService.ts | 3 +- 14 files changed, 933 insertions(+), 35 deletions(-) create mode 100644 packages/api/src/__tests__/e2e-worker-env.test.ts create mode 100644 packages/api/src/routes/__tests__/chat-e2e-stub.test.ts create mode 100644 packages/api/src/routes/__tests__/weather-e2e-stub.test.ts diff --git a/packages/api/scripts/e2e-local-start.ts b/packages/api/scripts/e2e-local-start.ts index 1d81abdcbc..76c4b753f4 100644 --- a/packages/api/scripts/e2e-local-start.ts +++ b/packages/api/scripts/e2e-local-start.ts @@ -1,4 +1,4 @@ -import { existsSync } from 'node:fs'; +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { nodeEnv } from '@packrat/env/node'; @@ -18,16 +18,29 @@ const composeEnv = { E2E_DB_PORT: e2eDbPort, }; -async function run(opts: { command: string[]; cwd?: string; env?: NodeJS.ProcessEnv }) { +async function run(opts: { + command: string[]; + cwd?: string; + env?: NodeJS.ProcessEnv; + captureOutput?: boolean; +}) { const { command } = opts; const child = Bun.spawn(command, { cwd: opts.cwd, env: opts.env ?? composeEnv, - stdout: 'inherit', - stderr: 'inherit', + stdout: opts.captureOutput ? 'pipe' : 'inherit', + stderr: opts.captureOutput ? 'pipe' : 'inherit', }); + const [stdout, stderr] = opts.captureOutput + ? await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text()]) + : ['', '']; const exitCode = await child.exited; if (exitCode !== 0) throw new Error(`${command.join(' ')} exited with ${exitCode}`); + if (opts.captureOutput) { + if (stdout) process.stdout.write(stdout); + if (stderr) process.stderr.write(stderr); + } + return `${stdout}${stderr}`; } async function succeeds(command: string[]) { @@ -59,6 +72,19 @@ function parseEnvFile(text: string) { return vars; } +function upsertEnvFileValue(input: { path: string; key: string; value: string }) { + const { path, key, value } = input; + const lines = readFileSync(path, 'utf8').replaceAll('\r\n', '\n').split('\n'); + let updated = false; + const nextLines = lines.map((line) => { + if (!line.trim().startsWith(`${key}=`)) return line; + updated = true; + return `${key}=${value}`; + }); + if (!updated) nextLines.push(`${key}=${value}`); + writeFileSync(path, nextLines.join('\n')); +} + if (!(await succeeds(['docker', '--version']))) { console.error('Error: Docker not found. Install Docker Desktop and try again.'); process.exit(1); @@ -119,7 +145,7 @@ const e2eEmail = nodeEnv.E2E_TEST_EMAIL ?? envFileVars.E2E_TEST_EMAIL ?? 'e2e@pa const e2ePass = nodeEnv.E2E_TEST_PASSWORD ?? envFileVars.E2E_TEST_PASSWORD ?? 'E2eTestPass123!'; console.log(`Seeding E2E test user (${e2eEmail})...`); -await run({ +const seedOutput = await run({ command: ['bun', 'run', 'db:seed:e2e-user'], cwd: apiDir, env: { @@ -128,7 +154,13 @@ await run({ E2E_TEST_EMAIL: e2eEmail, E2E_TEST_PASSWORD: e2ePass, }, + captureOutput: true, }); +const seededUserId = seedOutput.match(/\(id=([^)]+)\)/)?.[1]; +if (!seededUserId) { + throw new Error('Unable to resolve seeded E2E user id from db:seed:e2e-user output.'); +} +upsertEnvFileValue({ path: e2eVars, key: 'E2E_TEST_USER_ID', value: seededUserId }); console.log(''); console.log(`Starting local E2E API on http://localhost:${apiPort} ...`); @@ -143,7 +175,15 @@ const apiEnv = { NODE_ENV: 'test', NEON_DATABASE_URL: e2eDbUrl, NEON_DATABASE_URL_READONLY: e2eDbUrl, + E2E_TEST_EMAIL: e2eEmail, + E2E_TEST_PASSWORD: e2ePass, + E2E_TEST_USER_ID: seededUserId, BETTER_AUTH_URL: `http://127.0.0.1:${apiPort}`, + OPENAI_API_KEY: + nodeEnv.OPENAI_API_KEY ?? + (envFileVars.OPENAI_API_KEY === 'sk-test' ? 'sk-e2e-stub-local' : envFileVars.OPENAI_API_KEY), + WEATHER_API_KEY: nodeEnv.WEATHER_API_KEY ?? 'weather-e2e-stub-local', + APPLE_PRIVATE_KEY: nodeEnv.APPLE_PRIVATE_KEY ?? '', PACKRAT_PG_POOL_MAX: nodeEnv.PACKRAT_PG_POOL_MAX ?? envFileVars.PACKRAT_PG_POOL_MAX ?? '50', }; diff --git a/packages/api/src/__tests__/auth-cors.test.ts b/packages/api/src/__tests__/auth-cors.test.ts index d50518fa03..bab969b02a 100644 --- a/packages/api/src/__tests__/auth-cors.test.ts +++ b/packages/api/src/__tests__/auth-cors.test.ts @@ -1,3 +1,7 @@ +import { + oauthProviderAuthServerMetadata, + oauthProviderOpenIdConfigMetadata, +} from '@better-auth/oauth-provider'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const mocks = vi.hoisted(() => { @@ -111,6 +115,12 @@ const ctx = { beforeEach(() => { vi.clearAllMocks(); + vi.mocked(oauthProviderAuthServerMetadata).mockReturnValue( + vi.fn(async () => Response.json({ issuer: 'http://localhost:8787/api/auth' })), + ); + vi.mocked(oauthProviderOpenIdConfigMetadata).mockReturnValue( + vi.fn(async () => Response.json({ issuer: 'http://localhost:8787/api/auth' })), + ); }); describe('Worker /api/auth CORS', () => { @@ -140,4 +150,30 @@ describe('Worker /api/auth CORS', () => { expect(res.headers.get('Access-Control-Allow-Methods')).toContain('POST'); expect(mocks.getAuth).not.toHaveBeenCalled(); }); + + it('serves auth-mounted OAuth metadata for Better Auth discovery', async () => { + const fetch = worker.fetch; + if (!fetch) throw new Error('Worker fetch handler is not configured'); + + const request = new Request( + 'http://localhost:8787/.well-known/oauth-authorization-server/api/auth', + { + method: 'GET', + headers: { Origin: 'http://localhost:5173' }, + }, + ); + + const res = await fetch( + // safe-cast: Cloudflare's typed Request carries cf metadata; this path only reads URL, method, and headers. + request as never, + { ENVIRONMENT: 'development' } as never, + ctx as never, + ); + + expect(res.status).toBe(200); + await expect(res.json()).resolves.toMatchObject({ issuer: 'http://localhost:8787/api/auth' }); + expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://localhost:5173'); + expect(oauthProviderAuthServerMetadata).toHaveBeenCalled(); + expect(mocks.getAuth).toHaveBeenCalled(); + }); }); diff --git a/packages/api/src/__tests__/e2e-worker-env.test.ts b/packages/api/src/__tests__/e2e-worker-env.test.ts new file mode 100644 index 0000000000..0381692f5a --- /dev/null +++ b/packages/api/src/__tests__/e2e-worker-env.test.ts @@ -0,0 +1,106 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mocks = vi.hoisted(() => ({ + addCorsHeaders: vi.fn(({ response }) => response), + appFetch: vi.fn(async () => new Response('app')), + authHandler: vi.fn(async () => new Response('auth')), + corsPreflightResponse: vi.fn(() => null), + getAuth: vi.fn(), + getEnv: vi.fn(), + setWorkerEnv: vi.fn(), +})); + +vi.mock('@packrat/api/app', () => ({ + addCorsHeaders: mocks.addCorsHeaders, + appBase: { fetch: mocks.appFetch }, + corsPreflightResponse: mocks.corsPreflightResponse, +})); + +vi.mock('@packrat/api/auth', () => ({ + getAuth: mocks.getAuth, +})); + +vi.mock('@packrat/api/utils/env-validation', () => ({ + getEnv: mocks.getEnv, + setWorkerEnv: mocks.setWorkerEnv, +})); + +vi.mock('@better-auth/oauth-provider', () => ({ + oauthProviderAuthServerMetadata: vi.fn(() => mocks.authHandler), + oauthProviderOpenIdConfigMetadata: vi.fn(() => mocks.authHandler), +})); + +const worker = (await import('../e2e-worker')).default; + +const rawEnv = { + BETTER_AUTH_SECRET: 'e2e-better-auth-secret-at-least-32-chars', + BETTER_AUTH_URL: 'http://localhost:8787', + NODE_ENV: 'test', + OSM_HYPERDRIVE: { connectionString: 'postgres://user:pass@localhost/osm' }, +}; + +const validatedEnv = { + ...rawEnv, + OSM_DATABASE_URL: 'postgres://user:pass@localhost/osm', + PACKRAT_API_URL: 'http://localhost:8787', + PACKRAT_AUTH_SECRET: 'e2e-better-auth-secret-at-least-32-chars', +}; + +const ctx = { + passThroughOnException: vi.fn(), + waitUntil: vi.fn(), +} as unknown as ExecutionContext; + +beforeEach(() => { + vi.clearAllMocks(); + mocks.getEnv.mockReturnValue(validatedEnv); + mocks.getAuth.mockResolvedValue({ handler: mocks.authHandler }); +}); + +describe('e2e worker env normalization', () => { + it('normalizes env before Better Auth handles auth requests', async () => { + const request = new Request('http://localhost:8787/api/auth/sign-in/email', { + method: 'POST', + }); + + const response = await worker.fetch(request, rawEnv as never, ctx); + + expect(await response.text()).toBe('auth'); + expect(mocks.getEnv).toHaveBeenCalledWith({ + ...rawEnv, + OSM_DATABASE_URL: 'postgres://user:pass@localhost/osm', + }); + expect(mocks.setWorkerEnv).toHaveBeenCalledWith(validatedEnv); + expect(mocks.getAuth).toHaveBeenCalledWith(validatedEnv); + expect(mocks.authHandler).toHaveBeenCalledWith(request); + expect(mocks.addCorsHeaders).toHaveBeenCalledWith({ request, response }); + }); + + it('passes the normalized env to app routes', async () => { + const request = new Request('http://localhost:8787/api/packs'); + + const response = await worker.fetch(request, rawEnv as never, ctx); + + expect(await response.text()).toBe('app'); + expect(mocks.appFetch).toHaveBeenCalledWith(request, validatedEnv, ctx); + }); + + it('serves auth-mounted OAuth metadata with normalized env', async () => { + mocks.authHandler.mockResolvedValueOnce( + Response.json({ issuer: 'http://localhost:8787/api/auth' }), + ); + const request = new Request( + 'http://localhost:8787/.well-known/oauth-authorization-server/api/auth', + ); + + const response = await worker.fetch(request, rawEnv as never, ctx); + + expect(response.status).toBe(200); + await expect(response.json()).resolves.toMatchObject({ + issuer: 'http://localhost:8787/api/auth', + }); + expect(mocks.getAuth).toHaveBeenCalledWith(validatedEnv); + expect(mocks.authHandler).toHaveBeenCalledWith(request); + expect(mocks.appFetch).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/api/src/e2e-worker.ts b/packages/api/src/e2e-worker.ts index 73259dae73..f3599c9290 100644 --- a/packages/api/src/e2e-worker.ts +++ b/packages/api/src/e2e-worker.ts @@ -1,8 +1,15 @@ +import { + oauthProviderAuthServerMetadata, + oauthProviderOpenIdConfigMetadata, +} from '@better-auth/oauth-provider'; import type { MessageBatch } from '@cloudflare/workers-types'; import { addCorsHeaders, appBase, corsPreflightResponse } from '@packrat/api/app'; import { getAuth } from '@packrat/api/auth'; -import type { Env } from '@packrat/api/utils/env-validation'; -import { setWorkerEnv } from '@packrat/api/utils/env-validation'; +import { type Env, getEnv, setWorkerEnv } from '@packrat/api/utils/env-validation'; + +const WELL_KNOWN_AUTH_SERVER_PATH = '/.well-known/oauth-authorization-server'; +const WELL_KNOWN_OPENID_CONFIG_PATH = '/.well-known/openid-configuration'; +const WELL_KNOWN_AUTH_BASE_PATH = '/api/auth'; function enrichEnv(env: Env): Env { if (env.OSM_HYPERDRIVE) { @@ -11,13 +18,41 @@ function enrichEnv(env: Env): Env { return env; } +function wellKnownMetadataKind(pathname: string): 'openid' | 'authorization-server' | null { + if ( + pathname === WELL_KNOWN_OPENID_CONFIG_PATH || + pathname === `${WELL_KNOWN_OPENID_CONFIG_PATH}${WELL_KNOWN_AUTH_BASE_PATH}` + ) { + return 'openid'; + } + if ( + pathname === WELL_KNOWN_AUTH_SERVER_PATH || + pathname === `${WELL_KNOWN_AUTH_SERVER_PATH}${WELL_KNOWN_AUTH_BASE_PATH}` + ) { + return 'authorization-server'; + } + return null; +} + export default { // biome-ignore lint/complexity/useMaxParams: Cloudflare Worker fetch callbacks receive request, env, and context. async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { - const e = enrichEnv(env); - setWorkerEnv(Object.assign({}, e)); + const e = getEnv(enrichEnv(env) as unknown as Record); + setWorkerEnv(e as unknown as Record); const url = new URL(request.url); + if (request.method === 'GET') { + const metadataKind = wellKnownMetadataKind(url.pathname); + if (metadataKind) { + const auth = await getAuth(e); + const handler = + metadataKind === 'openid' + ? oauthProviderOpenIdConfigMetadata(auth) + : oauthProviderAuthServerMetadata(auth); + return handler(request); + } + } + if (url.pathname.startsWith('/api/auth')) { const preflight = corsPreflightResponse(request); if (preflight) return preflight; diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 58eddaa50c..98015df4f6 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -49,6 +49,9 @@ const bearerPrefixRegex = /^Bearer\s+/i; const scopeSplitRegex = /\s+/; const OAUTH_CONSENT_PATH = '/api/auth/oauth2/consent'; const OAUTH_AUTHORIZE_PATH = '/api/auth/oauth2/authorize'; +const WELL_KNOWN_AUTH_SERVER_PATH = '/.well-known/oauth-authorization-server'; +const WELL_KNOWN_OPENID_CONFIG_PATH = '/.well-known/openid-configuration'; +const WELL_KNOWN_AUTH_BASE_PATH = '/api/auth'; const WELL_KNOWN_ALLOWED_ORIGINS = new Set(['https://claude.ai', 'https://claude.com']); /** localhost origins get CORS on the well-known endpoints so MCP Inspector can drive OAuth discovery. */ const LOCALHOST_WELL_KNOWN_ORIGIN = /^http:\/\/localhost:\d+$/; @@ -126,12 +129,7 @@ async function handleLocalE2EAuth(request: Request, env: Env): Promise ({ + getAuth: vi.fn(), +})); + +vi.mock('@packrat/api/auth/local-e2e', () => ({ + getLocalE2EUserFromRequest: vi.fn(async () => null), +})); + +vi.mock('@packrat/api/auth/mcp-token', () => ({ + resolveMcpBearerUser: vi.fn(async () => null), +})); + +vi.mock('@packrat/api/db', () => ({ + createDb: vi.fn(), +})); + +vi.mock('@packrat/api/utils/ai/provider', () => ({ + createAIProvider: vi.fn(), +})); + +vi.mock('@packrat/api/utils/ai/tools', () => ({ + createTools: vi.fn(() => ({})), +})); + +vi.mock('@packrat/api/utils/DbUtils', () => ({ + getSchemaInfo: vi.fn(async () => 'e2e schema'), +})); + +vi.mock('@packrat/api/utils/env-validation', () => ({ + getEnv: vi.fn(), +})); + +vi.mock('@packrat/api/utils/queryMetrics', () => ({ + setQueryMetricsUser: vi.fn(), +})); + +vi.mock('@packrat/api/utils/sentry', () => ({ + apiAddBreadcrumb: vi.fn(), + captureApiException: vi.fn(), + setApiUser: vi.fn(), +})); + +const mockGetAuth = getAuth as unknown as ReturnType; +const mockGetEnv = getEnv as unknown as ReturnType; +const mockCreateAIProvider = createAIProvider as unknown as ReturnType; + +const app = new Elysia().use(chatRoutes); + +beforeEach(() => { + vi.clearAllMocks(); + mockGetEnv.mockReturnValue({ + AI_PROVIDER: 'openai', + OPENAI_API_KEY: 'sk-test', + } as never); + mockGetAuth.mockResolvedValue({ + api: { + getSession: vi.fn(async () => ({ + user: { + id: 'test-user-id', + email: 'swift-e2e@example.com', + name: 'Swift E2E', + role: 'USER', + }, + })), + }, + } as never); +}); + +describe('chat E2E stub route', () => { + it('serves deterministic chat stream content for local stub keys', async () => { + const response = await app.handle( + new Request('http://localhost/chat', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + date: '2026-07-17', + messages: [ + { + id: 'test-message', + role: 'user', + parts: [{ type: 'text', text: 'Hi' }], + }, + ], + }), + }), + ); + + expect(response.status).toBe(200); + await expect(response.text()).resolves.toContain('three essential items'); + expect(mockCreateAIProvider).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/api/src/routes/__tests__/weather-e2e-stub.test.ts b/packages/api/src/routes/__tests__/weather-e2e-stub.test.ts new file mode 100644 index 0000000000..103c413846 --- /dev/null +++ b/packages/api/src/routes/__tests__/weather-e2e-stub.test.ts @@ -0,0 +1,102 @@ +import { getAuth } from '@packrat/api/auth'; +import { weatherRoutes } from '@packrat/api/routes/weather'; +import { getEnv } from '@packrat/api/utils/env-validation'; +import { captureApiException } from '@packrat/api/utils/sentry'; +import { Elysia } from 'elysia'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@packrat/api/auth', () => ({ + getAuth: vi.fn(), +})); + +vi.mock('@packrat/api/auth/local-e2e', () => ({ + getLocalE2EUserFromRequest: vi.fn(async () => null), +})); + +vi.mock('@packrat/api/auth/mcp-token', () => ({ + resolveMcpBearerUser: vi.fn(async () => null), +})); + +vi.mock('@packrat/api/utils/env-validation', () => ({ + getEnv: vi.fn(), +})); + +vi.mock('@packrat/api/utils/queryMetrics', () => ({ + setQueryMetricsUser: vi.fn(), +})); + +vi.mock('@packrat/api/utils/sentry', () => ({ + apiAddBreadcrumb: vi.fn(), + captureApiException: vi.fn(), + setApiUser: vi.fn(), +})); + +const mockGetAuth = vi.mocked(getAuth); +const mockGetEnv = vi.mocked(getEnv); +const mockCaptureApiException = vi.mocked(captureApiException); +const mockFetch = vi.fn(); + +const app = new Elysia().use(weatherRoutes); + +beforeEach(() => { + vi.clearAllMocks(); + mockGetEnv.mockReturnValue({ + WEATHER_API_KEY: 'weather-e2e-stub-local', + } as never); + mockGetAuth.mockResolvedValue({ + api: { + getSession: vi.fn(async () => ({ + user: { + id: 'test-user-id', + email: 'swift-e2e@example.com', + name: 'Swift E2E', + role: 'USER', + }, + })), + }, + } as never); + vi.stubGlobal('fetch', mockFetch); +}); + +describe('weather E2E stub routes', () => { + it('serves deterministic search and forecast data without calling WeatherAPI', async () => { + const search = await app.handle(new Request('http://localhost/weather/search?q=Denver')); + expect(search.status).toBe(200); + const locations = (await search.json()) as Array<{ id: number; name: string }>; + + expect(locations[0]).toMatchObject({ id: 5419384, name: 'Denver' }); + + const forecast = await app.handle( + new Request(`http://localhost/weather/forecast?id=${locations[0]?.id ?? 0}`), + ); + expect(forecast.status).toBe(200); + const body = (await forecast.json()) as { + location: { id: number; name: string }; + current: { temp_f: number }; + forecast: { forecastday: unknown[] }; + }; + + expect(body.location).toMatchObject({ id: 5419384, name: 'Denver' }); + expect(body.current.temp_f).toBe(72); + expect(body.forecast.forecastday).toHaveLength(10); + expect(mockFetch).not.toHaveBeenCalled(); + expect(mockCaptureApiException).not.toHaveBeenCalled(); + }); + + it('serves by-name forecast data and not-found responses deterministically', async () => { + const yosemite = await app.handle(new Request('http://localhost/weather/by-name?q=Yosemite')); + expect(yosemite.status).toBe(200); + await expect(yosemite.json()).resolves.toMatchObject({ + location: { name: 'Yosemite Valley', region: 'California' }, + current: { temp_f: 68 }, + }); + + const missing = await app.handle(new Request('http://localhost/weather/by-name?q=Atlantis')); + expect(missing.status).toBe(404); + await expect(missing.json()).resolves.toMatchObject({ + error: 'No weather location matched "Atlantis"', + }); + expect(mockFetch).not.toHaveBeenCalled(); + expect(mockCaptureApiException).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/api/src/routes/catalog/index.ts b/packages/api/src/routes/catalog/index.ts index 86fcecc62d..963d48d011 100644 --- a/packages/api/src/routes/catalog/index.ts +++ b/packages/api/src/routes/catalog/index.ts @@ -38,6 +38,74 @@ import { import { Elysia, NotFoundError, status } from 'elysia'; import { z } from 'zod'; +const isLocalE2ECatalogEnv = () => { + const { NEON_DATABASE_URL, OPENAI_API_KEY } = getEnv(); + return ( + (OPENAI_API_KEY === 'sk-test' || OPENAI_API_KEY?.startsWith('sk-e2e-stub-') === true) && + (NEON_DATABASE_URL.includes('127.0.0.1') || NEON_DATABASE_URL.includes('localhost')) + ); +}; + +const localE2ECatalogItems = [ + { + id: 7001, + name: 'Copper Spur HV UL2 Tent', + productUrl: 'https://example.test/catalog/copper-spur', + sku: 'E2E-COPPER-SPUR', + weight: 1420, + weightUnit: 'g', + description: + 'Freestanding two-person backpacking tent used for deterministic local E2E search.', + categories: ['shelter', 'backpacking'], + images: [] as string[], + brand: 'Big Agnes', + model: 'Copper Spur HV UL2', + ratingValue: 4.8, + color: 'Orange', + size: '2 person', + price: 549.95, + availability: 'in_stock', + seller: 'PackRat E2E', + productSku: 'E2E-COPPER-SPUR', + material: 'Nylon', + currency: 'USD', + condition: 'new', + reviewCount: 42, + usageCount: 8, + createdAt: '2026-01-01T00:00:00.000Z', + updatedAt: '2026-01-01T00:00:00.000Z', + similarity: 0.92, + }, + { + id: 7002, + name: 'Hyperlite 40L Pack', + productUrl: 'https://example.test/catalog/hyperlite-40', + sku: 'E2E-HYPERLITE-40', + weight: 910, + weightUnit: 'g', + description: 'Lightweight framed pack fixture for local E2E similar gear results.', + categories: ['pack', 'backpacking'], + images: [] as string[], + brand: 'Hyperlite', + model: '40L', + ratingValue: 4.6, + color: 'White', + size: '40 L', + price: 379, + availability: 'in_stock', + seller: 'PackRat E2E', + productSku: 'E2E-HYPERLITE-40', + material: 'Dyneema composite', + currency: 'USD', + condition: 'new', + reviewCount: 27, + usageCount: 5, + createdAt: '2026-01-02T00:00:00.000Z', + updatedAt: '2026-01-02T00:00:00.000Z', + similarity: 0.84, + }, +]; + export const catalogRoutes = new Elysia({ prefix: '/catalog' }) .model({ 'catalog.CatalogCategoriesResponse': CatalogCategoriesResponseSchema, @@ -105,6 +173,32 @@ export const catalogRoutes = new Elysia({ prefix: '/catalog' }) async ({ query }) => { try { const { q: searchQuery, limit = 10, offset = 0 } = query; + if (isLocalE2ECatalogEnv()) { + const normalizedQuery = searchQuery.trim().toLowerCase(); + const matched = localE2ECatalogItems.filter((item) => { + const haystack = [ + item.name, + item.brand, + item.model, + item.description, + ...(item.categories ?? []), + ] + .filter(Boolean) + .join(' ') + .toLowerCase(); + return haystack.includes(normalizedQuery) || normalizedQuery.length === 0; + }); + const items = matched.slice(offset, offset + limit); + + return { + items, + total: matched.length, + limit, + offset, + nextOffset: offset + items.length, + }; + } + const catalogService = new CatalogService(); return await catalogService.vectorSearch({ q: searchQuery, opts: { limit, offset } }); } catch (error) { diff --git a/packages/api/src/routes/chat.ts b/packages/api/src/routes/chat.ts index 0353afc1ac..329167fd67 100644 --- a/packages/api/src/routes/chat.ts +++ b/packages/api/src/routes/chat.ts @@ -25,7 +25,7 @@ import { DEFAULT_MODELS } from '../utils/ai/models'; import { getSchemaInfo } from '../utils/DbUtils'; const isE2EStubOpenAiKey = (openAiApiKey: string | undefined) => - openAiApiKey?.startsWith('sk-e2e-stub-') === true; + openAiApiKey === 'sk-test' || openAiApiKey?.startsWith('sk-e2e-stub-') === true; export const chatRoutes = new Elysia({ prefix: '/chat' }) .model({ diff --git a/packages/api/src/routes/guides/index.ts b/packages/api/src/routes/guides/index.ts index 7254801324..07409baaf0 100644 --- a/packages/api/src/routes/guides/index.ts +++ b/packages/api/src/routes/guides/index.ts @@ -10,14 +10,66 @@ import { GuidesQuerySchema, GuidesResponseSchema, } from '@packrat/schemas/guides'; - -const MDX_EXT_RE = /\.(mdx?|md)$/; -const DASH_RE = /-/g; - import { Elysia, NotFoundError, status } from 'elysia'; import matter from 'gray-matter'; import { z } from 'zod'; +const MDX_EXT_RE = /\.(mdx?|md)$/; +const DASH_RE = /-/g; +const isLocalE2EGuidesEnv = () => { + const { E2E_TEST_USER_ID, NEON_DATABASE_URL } = getEnv(); + return ( + Boolean(E2E_TEST_USER_ID) && + (NEON_DATABASE_URL.includes('127.0.0.1') || NEON_DATABASE_URL.includes('localhost')) + ); +}; + +const localE2EGuides = [ + { + id: 'e2e-layering-for-shoulder-season', + key: 'e2e-layering-for-shoulder-season.mdx', + title: 'Layering for Shoulder Season', + category: 'skills', + categories: ['skills', 'clothing'], + description: 'How to tune warmth, rain protection, and ventilation for variable trail days.', + author: 'PackRat', + readingTime: 6, + difficulty: 'beginner', + content: + 'Shoulder season trips work best with a breathable base layer, active insulation, rain protection, and a dry sleep layer. Keep the rain shell and headlamp easy to reach.', + createdAt: '2026-01-01T00:00:00.000Z', + updatedAt: '2026-01-01T00:00:00.000Z', + }, + { + id: 'e2e-lightweight-weekend-pack', + key: 'e2e-lightweight-weekend-pack.mdx', + title: 'Lightweight Weekend Pack', + category: 'packing', + categories: ['packing', 'backpacking'], + description: 'A compact framework for balancing comfort, safety, and low carried weight.', + author: 'PackRat', + readingTime: 5, + difficulty: 'intermediate', + content: + 'Start with shelter, sleep, water, food, layers, navigation, and repair. Cut duplicate comfort items only after essentials are covered.', + createdAt: '2026-01-02T00:00:00.000Z', + updatedAt: '2026-01-02T00:00:00.000Z', + }, +]; + +function paginateGuides(input: { items: T[]; page: number; limit: number }) { + const { items, page, limit } = input; + const totalCount = items.length; + const offset = (page - 1) * limit; + return { + items: items.slice(offset, offset + limit), + totalCount, + page, + limit, + totalPages: Math.ceil(totalCount / limit), + }; +} + export const guidesRoutes = new Elysia({ prefix: '/guides' }) .model({ 'guides.GuideCategoriesResponse': GuideCategoriesResponseSchema, @@ -54,6 +106,24 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) : undefined; try { + if (isLocalE2EGuidesEnv()) { + let guides = localE2EGuides.map(({ content: _content, ...guide }) => guide); + if (category) { + guides = guides.filter( + (guide) => guide.category === category || guide.categories.includes(category), + ); + } + guides.sort((a, b) => { + if (!sort) return a.title.localeCompare(b.title); + const aValue = String(a[sort.field as keyof typeof a]); + const bValue = String(b[sort.field as keyof typeof b]); + return sort.order === 'asc' + ? aValue.localeCompare(bValue) + : bValue.localeCompare(aValue); + }); + return GuidesResponseSchema.parse(paginateGuides({ items: guides, page, limit })); + } + const bucket = new R2BucketService({ env: getEnv(), bucketType: 'guides', @@ -152,6 +222,13 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) '/categories', async () => { try { + if (isLocalE2EGuidesEnv()) { + const categories = [ + ...new Set(localE2EGuides.flatMap((guide) => guide.categories)), + ].sort(); + return GuideCategoriesResponseSchema.parse({ categories, count: categories.length }); + } + const bucket = new R2BucketService({ env: getEnv(), bucketType: 'guides', @@ -212,6 +289,30 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) const searchQuery = q.toLowerCase(); try { + if (isLocalE2EGuidesEnv()) { + const guides = localE2EGuides + .filter( + (guide) => + !category || guide.category === category || guide.categories.includes(category), + ) + .map((guide) => { + let score = 0; + if (guide.title.toLowerCase().includes(searchQuery)) score += 10; + if (guide.description.toLowerCase().includes(searchQuery)) score += 5; + if (guide.content.toLowerCase().includes(searchQuery)) score += 1; + const { content: _content, ...summary } = guide; + return score > 0 ? { ...summary, score } : null; + }) + .filter((guide): guide is NonNullable => guide !== null) + .sort((a, b) => b.score - a.score) + .map(({ score: _score, ...guide }) => guide); + + return GuideSearchResponseSchema.parse({ + ...paginateGuides({ items: guides, page, limit }), + query: q, + }); + } + const bucket = new R2BucketService({ env: getEnv(), bucketType: 'guides', @@ -307,6 +408,12 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) const { id } = params; try { + if (isLocalE2EGuidesEnv()) { + const guide = localE2EGuides.find((candidate) => candidate.id === id); + if (!guide) throw new NotFoundError('Guide not found'); + return GuideDetailSchema.parse(guide); + } + const bucket = new R2BucketService({ env: getEnv(), bucketType: 'guides', diff --git a/packages/api/src/routes/seasonSuggestions.ts b/packages/api/src/routes/seasonSuggestions.ts index 8ba1f2b249..ca7b4599c2 100644 --- a/packages/api/src/routes/seasonSuggestions.ts +++ b/packages/api/src/routes/seasonSuggestions.ts @@ -10,6 +10,13 @@ import { Elysia, status } from 'elysia'; import { z } from 'zod'; import { DEFAULT_MODELS } from '../utils/ai/models'; +const isE2EStubOpenAiKey = (openAiApiKey: string | undefined) => + openAiApiKey?.startsWith('sk-e2e-stub-') === true; + +const isLocalE2ESeasonSuggestionEnv = (openAiApiKey: string | undefined, databaseUrl: string) => + (isE2EStubOpenAiKey(openAiApiKey) || openAiApiKey === 'sk-test') && + (databaseUrl.includes('127.0.0.1') || databaseUrl.includes('localhost')); + /** * Formats user inventory items for AI processing */ @@ -31,8 +38,79 @@ export const seasonSuggestionsRoutes = new Elysia({ prefix: '/season-suggestions '/', async ({ body, user }) => { const { location, date } = body; - const db = createDb(); + const { + OPENAI_API_KEY, + AI_PROVIDER, + CLOUDFLARE_ACCOUNT_ID, + CLOUDFLARE_AI_GATEWAY_ID, + CLOUDFLARE_API_TOKEN, + AI, + NEON_DATABASE_URL, + } = getEnv(); + + if (isLocalE2ESeasonSuggestionEnv(OPENAI_API_KEY, NEON_DATABASE_URL)) { + const suggestionItems = [ + { + id: 'e2e-season-rain-shell', + name: 'Rain shell', + description: 'Waterproof layer for shoulder-season weather.', + weight: 210, + weightUnit: 'g', + quantity: 1, + category: 'clothing', + consumable: false, + worn: false, + image: null, + notes: 'Keep this accessible for changing weather.', + catalogItemId: null, + }, + { + id: 'e2e-season-headlamp', + name: 'Headlamp', + description: 'Reliable lighting for short autumn daylight.', + weight: 85, + weightUnit: 'g', + quantity: 1, + category: 'lighting', + consumable: false, + worn: false, + image: null, + notes: 'Pack fresh batteries before leaving.', + catalogItemId: null, + }, + { + id: 'e2e-season-warm-layer', + name: 'Warm layer', + description: 'Insulating layer for cool evenings and exposed breaks.', + weight: 320, + weightUnit: 'g', + quantity: 1, + category: 'clothing', + consumable: false, + worn: true, + image: null, + notes: 'Wear or keep near the top of the pack.', + catalogItemId: null, + }, + ]; + return { + suggestions: [ + { + name: 'Shoulder Season Overnight', + description: `Balanced kit for ${location} with warmth, rain protection, and reliable camp basics.`, + category: 'backpacking', + tags: ['e2e', 'shoulder season', 'overnight'], + items: suggestionItems, + }, + ], + totalInventoryItems: suggestionItems.length, + location, + season: 'fall', + }; + } + + const db = createDb(); const items = await db.tag('seasonSuggestions.getUserInventory').query.packItems.findMany({ where: and(eq(packItems.userId, user.userId), eq(packItems.deleted, false)), columns: { embedding: false }, @@ -56,14 +134,6 @@ Date: ${date} Available Inventory Items: ${inventoryFormatted}`; - const { - OPENAI_API_KEY, - AI_PROVIDER, - CLOUDFLARE_ACCOUNT_ID, - CLOUDFLARE_AI_GATEWAY_ID, - CLOUDFLARE_API_TOKEN, - AI, - } = getEnv(); const aiProvider = createAIProvider({ openAiApiKey: OPENAI_API_KEY, provider: AI_PROVIDER, diff --git a/packages/api/src/routes/weather.ts b/packages/api/src/routes/weather.ts index 083436e358..eeb801425c 100644 --- a/packages/api/src/routes/weather.ts +++ b/packages/api/src/routes/weather.ts @@ -17,6 +17,142 @@ import { Elysia, status } from 'elysia'; import { ZodError } from 'zod'; const WEATHER_API_BASE_URL = 'https://api.weatherapi.com/v1'; +const COORDINATE_QUERY_REGEX = /^-?\d+(\.\d+)?,-?\d+(\.\d+)?$/; + +const isE2EWeatherStubKey = (key?: string) => key?.startsWith('weather-e2e-stub-') === true; + +const stubCondition = { + text: 'Partly cloudy', + icon: '//cdn.weatherapi.com/weather/64x64/day/116.png', + code: 1003, +}; + +const buildStubCurrent = (tempF = 72): WeatherAPIForecastResponse['current'] => ({ + last_updated: '2026-07-16 12:00', + temp_c: Math.round(((tempF - 32) * 5) / 9), + temp_f: tempF, + condition: stubCondition, + wind_mph: 7, + wind_kph: 11, + wind_degree: 240, + wind_dir: 'WSW', + pressure_mb: 1016, + pressure_in: 30.0, + precip_mm: 0, + precip_in: 0, + humidity: 32, + cloud: 25, + feelslike_c: Math.round(((tempF - 32) * 5) / 9), + feelslike_f: tempF, + vis_km: 16, + vis_miles: 10, + uv: 5, + is_day: 1, + gust_mph: 14, + gust_kph: 22, +}); + +const buildStubForecastDay = ( + dayOffset: number, +): WeatherAPIForecastResponse['forecast']['forecastday'][number] => { + const date = new Date(Date.UTC(2026, 6, 16 + dayOffset)); + const highF = 78 + (dayOffset % 3); + const lowF = 55 + (dayOffset % 2); + return { + date: date.toISOString().slice(0, 10), + date_epoch: Math.floor(date.getTime() / 1000), + day: { + maxtemp_c: Math.round(((highF - 32) * 5) / 9), + maxtemp_f: highF, + mintemp_c: Math.round(((lowF - 32) * 5) / 9), + mintemp_f: lowF, + avgtemp_c: Math.round((((highF + lowF) / 2 - 32) * 5) / 9), + avgtemp_f: (highF + lowF) / 2, + maxwind_mph: 18, + maxwind_kph: 29, + totalprecip_mm: dayOffset === 2 ? 1.2 : 0, + totalprecip_in: dayOffset === 2 ? 0.05 : 0, + totalsnow_cm: 0, + avghumidity: 35, + avgvis_km: 16, + avgvis_miles: 10, + uv: 5, + condition: stubCondition, + daily_chance_of_rain: dayOffset === 2 ? 30 : 5, + daily_chance_of_snow: 0, + }, + astro: { + sunrise: '05:48 AM', + sunset: '08:27 PM', + moonrise: '10:15 PM', + moonset: '07:12 AM', + moon_phase: 'Waxing Crescent', + moon_illumination: 18, + }, + hour: [], + }; +}; + +type StubWeatherLocation = WeatherAPISearchResponse[number]; + +const defaultStubLocation: StubWeatherLocation = { + id: 5419384, + name: 'Denver', + region: 'Colorado', + country: 'United States', + lat: 39.74, + lon: -104.98, + url: 'denver-colorado-united-states-of-america', +}; + +const stubLocations: [StubWeatherLocation, ...StubWeatherLocation[]] = [ + defaultStubLocation, + { + id: 5400000, + name: 'Yosemite Valley', + region: 'California', + country: 'United States', + lat: 37.75, + lon: -119.59, + url: 'yosemite-valley-california-united-states-of-america', + }, +] satisfies [StubWeatherLocation, ...StubWeatherLocation[]]; + +const getStubLocation = (id?: number) => + stubLocations.find((location) => location.id === id) ?? defaultStubLocation; + +const buildStubForecast = (id?: number): WeatherAPIForecastResponse => { + const location = getStubLocation(id); + return { + location: { + id: location.id, + name: location.name, + region: location.region, + country: location.country, + lat: location.lat, + lon: location.lon, + tz_id: location.name === 'Denver' ? 'America/Denver' : 'America/Los_Angeles', + localtime_epoch: 1784246400, + localtime: '2026-07-16 12:00', + }, + current: buildStubCurrent(location.name === 'Denver' ? 72 : 68), + forecast: { + forecastday: Array.from({ length: 10 }, (_, index) => buildStubForecastDay(index)), + }, + alerts: { alert: [] }, + }; +}; + +const searchStubLocations = (query: string) => { + const normalizedQuery = query.trim().toLowerCase(); + if (!normalizedQuery) return []; + if (COORDINATE_QUERY_REGEX.test(normalizedQuery)) return [defaultStubLocation]; + return stubLocations.filter((location) => + `${location.name} ${location.region} ${location.country}` + .toLowerCase() + .includes(normalizedQuery), + ); +}; export const weatherRoutes = new Elysia({ prefix: '/weather' }) .model({ @@ -33,6 +169,17 @@ export const weatherRoutes = new Elysia({ prefix: '/weather' }) return status(400, { error: 'Query parameter is required' }); } + if (isE2EWeatherStubKey(WEATHER_API_KEY)) { + return searchStubLocations(q).map((item) => ({ + id: item.id, + name: item.name, + region: item.region, + country: item.country, + lat: item.lat, + lon: item.lon, + })); + } + try { const response = await fetch( `${WEATHER_API_BASE_URL}/search.json?key=${WEATHER_API_KEY}&q=${encodeURIComponent(q)}`, @@ -83,6 +230,19 @@ export const weatherRoutes = new Elysia({ prefix: '/weather' }) }); } + if (isE2EWeatherStubKey(WEATHER_API_KEY)) { + return [ + { + id: defaultStubLocation.id, + name: defaultStubLocation.name, + region: defaultStubLocation.region, + country: defaultStubLocation.country, + lat: defaultStubLocation.lat, + lon: defaultStubLocation.lon, + }, + ]; + } + try { const q = `${latitude.toFixed(6)},${longitude.toFixed(6)}`; const response = await fetch( @@ -156,6 +316,10 @@ export const weatherRoutes = new Elysia({ prefix: '/weather' }) return status(400, { error: 'Valid location ID is required' }); } + if (isE2EWeatherStubKey(WEATHER_API_KEY)) { + return WeatherAPIForecastResponseSchema.parse(buildStubForecast(id)); + } + try { const q = `id:${id}`; const response = await fetch( @@ -220,6 +384,15 @@ export const weatherRoutes = new Elysia({ prefix: '/weather' }) // Schema enforces z.string().min(2); Elysia rejects shorter values // before the handler runs. const q = query.q; + + if (isE2EWeatherStubKey(WEATHER_API_KEY)) { + const firstMatch = first(searchStubLocations(q)); + if (!firstMatch) { + return status(404, { error: `No weather location matched "${q}"` }); + } + return WeatherAPIForecastResponseSchema.parse(buildStubForecast(firstMatch.id)); + } + try { const searchResponse = await fetch( `${WEATHER_API_BASE_URL}/search.json?key=${WEATHER_API_KEY}&q=${encodeURIComponent(q)}`, diff --git a/packages/api/src/services/__tests__/embeddingService.test.ts b/packages/api/src/services/__tests__/embeddingService.test.ts index d412a73bb5..b689e74493 100644 --- a/packages/api/src/services/__tests__/embeddingService.test.ts +++ b/packages/api/src/services/__tests__/embeddingService.test.ts @@ -136,6 +136,20 @@ describe('embeddingService', () => { expect(first).toHaveLength(1536); expect(first).toEqual(second); }); + + it('returns deterministic embeddings for the local sk-test key', async () => { + const { embed } = await import('ai'); + const mockEmbed = embed as ReturnType; + + const result = await generateEmbedding({ + ...baseParams, + openAiApiKey: 'sk-test', + value: 'local e2e item', + }); + + expect(result).toHaveLength(1536); + expect(mockEmbed).not.toHaveBeenCalled(); + }); }); describe('generateManyEmbeddings', () => { diff --git a/packages/api/src/services/embeddingService.ts b/packages/api/src/services/embeddingService.ts index d0b11b8ac5..65ad7b4097 100644 --- a/packages/api/src/services/embeddingService.ts +++ b/packages/api/src/services/embeddingService.ts @@ -20,7 +20,8 @@ type GenerateEmbeddingParams = GenerateEmbeddingBaseParams & { value: string; }; -const isE2EStubKey = (key?: string) => key?.startsWith('sk-e2e-stub-') === true; +const isE2EStubKey = (key?: string) => + key === 'sk-test' || key?.startsWith('sk-e2e-stub-') === true; const deterministicEmbedding = (value: string): number[] => { let hash = 2166136261; From 1eb63349a863bde31181eb877452679585d3150c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 07:03:29 -0600 Subject: [PATCH 10/81] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Add=20Swift=20dep?= =?UTF-8?q?loyed=20test=20controls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 83 ++++++++++++++++++++- .github/workflows/swift-visual.yml | 49 ++++++++++-- apps/swift/scripts/generate-swift-config.ts | 52 ++++++++++++- 3 files changed, 174 insertions(+), 10 deletions(-) diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index f47e7e5e2b..37c66af619 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -35,6 +35,25 @@ on: required: false type: boolean default: false + api_environment: + description: "API target for manual runs" + required: false + type: choice + default: configured-secret + options: + - configured-secret + - local + - dev + - production + feature_flag_profile: + description: "Temporary Swift feature flag profile" + required: false + type: choice + default: default + options: + - default + - all-on + - all-off concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -79,13 +98,40 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile + - name: Resolve Swift E2E target + run: | + target="${{ inputs.api_environment || 'configured-secret' }}" + case "$target" in + configured-secret) + echo "PACKRAT_ENV=dev" >> "$GITHUB_ENV" + ;; + local) + echo "PACKRAT_ENV=local" >> "$GITHUB_ENV" + echo "E2E_API_BASE_URL=" >> "$GITHUB_ENV" + ;; + dev) + echo "PACKRAT_ENV=dev" >> "$GITHUB_ENV" + echo "E2E_API_BASE_URL=https://packrat-api-dev.orange-frost-d665.workers.dev" >> "$GITHUB_ENV" + ;; + production) + echo "PACKRAT_ENV=production" >> "$GITHUB_ENV" + echo "E2E_API_BASE_URL=https://packrat-api.orange-frost-d665.workers.dev" >> "$GITHUB_ENV" + ;; + esac + echo "PACKRAT_SWIFT_FEATURE_FLAG_PROFILE=${{ inputs.feature_flag_profile || 'default' }}" >> "$GITHUB_ENV" + - name: Verify Swift E2E secrets run: | + target="${{ inputs.api_environment || 'configured-secret' }}" missing=() [ -z "${E2E_EMAIL:-}" ] && missing+=("E2E_TEST_EMAIL") [ -z "${E2E_PASSWORD:-}" ] && missing+=("E2E_TEST_PASSWORD") - [ -z "${E2E_API_BASE_URL:-}" ] && missing+=("SWIFT_E2E_API_BASE_URL") - [ -z "${NEON_DATABASE_URL:-}" ] && missing+=("NEON_DEV_DATABASE_URL") + if [ "$target" = "configured-secret" ] && [ -z "${E2E_API_BASE_URL:-}" ]; then + missing+=("SWIFT_E2E_API_BASE_URL") + fi + if [ "$target" != "local" ] && [ "$target" != "production" ] && [ -z "${NEON_DATABASE_URL:-}" ]; then + missing+=("NEON_DEV_DATABASE_URL") + fi if [ ${#missing[@]} -gt 0 ]; then echo "::error::Required Swift E2E secrets missing: ${missing[*]}" exit 1 @@ -101,6 +147,7 @@ jobs: run: bun run swift - name: Seed E2E test user + if: ${{ (inputs.api_environment || 'configured-secret') != 'local' && (inputs.api_environment || 'configured-secret') != 'production' }} run: bun run --filter @packrat/api db:seed:e2e-user env: NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }} @@ -179,13 +226,40 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile + - name: Resolve Swift E2E target + run: | + target="${{ inputs.api_environment || 'configured-secret' }}" + case "$target" in + configured-secret) + echo "PACKRAT_ENV=dev" >> "$GITHUB_ENV" + ;; + local) + echo "PACKRAT_ENV=local" >> "$GITHUB_ENV" + echo "E2E_API_BASE_URL=" >> "$GITHUB_ENV" + ;; + dev) + echo "PACKRAT_ENV=dev" >> "$GITHUB_ENV" + echo "E2E_API_BASE_URL=https://packrat-api-dev.orange-frost-d665.workers.dev" >> "$GITHUB_ENV" + ;; + production) + echo "PACKRAT_ENV=production" >> "$GITHUB_ENV" + echo "E2E_API_BASE_URL=https://packrat-api.orange-frost-d665.workers.dev" >> "$GITHUB_ENV" + ;; + esac + echo "PACKRAT_SWIFT_FEATURE_FLAG_PROFILE=${{ inputs.feature_flag_profile || 'default' }}" >> "$GITHUB_ENV" + - name: Verify Swift E2E secrets run: | + target="${{ inputs.api_environment || 'configured-secret' }}" missing=() [ -z "${E2E_EMAIL:-}" ] && missing+=("E2E_TEST_EMAIL") [ -z "${E2E_PASSWORD:-}" ] && missing+=("E2E_TEST_PASSWORD") - [ -z "${E2E_API_BASE_URL:-}" ] && missing+=("SWIFT_E2E_API_BASE_URL") - [ -z "${NEON_DATABASE_URL:-}" ] && missing+=("NEON_DEV_DATABASE_URL") + if [ "$target" = "configured-secret" ] && [ -z "${E2E_API_BASE_URL:-}" ]; then + missing+=("SWIFT_E2E_API_BASE_URL") + fi + if [ "$target" != "local" ] && [ "$target" != "production" ] && [ -z "${NEON_DATABASE_URL:-}" ]; then + missing+=("NEON_DEV_DATABASE_URL") + fi if [ ${#missing[@]} -gt 0 ]; then echo "::error::Required Swift E2E secrets missing: ${missing[*]}" exit 1 @@ -197,6 +271,7 @@ jobs: run: bun run swift - name: Seed E2E test user + if: ${{ (inputs.api_environment || 'configured-secret') != 'local' && (inputs.api_environment || 'configured-secret') != 'production' }} run: bun run --filter @packrat/api db:seed:e2e-user env: NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }} diff --git a/.github/workflows/swift-visual.yml b/.github/workflows/swift-visual.yml index 59a6e90e9b..e84ef75355 100644 --- a/.github/workflows/swift-visual.yml +++ b/.github/workflows/swift-visual.yml @@ -8,13 +8,33 @@ on: platform: description: "Which visual suite to run" required: true - default: both + default: all type: choice options: + - all - both - ios - ipad - macos + - watch + api_environment: + description: "API target for screenshot capture" + required: false + type: choice + default: local + options: + - local + - dev + - production + feature_flag_profile: + description: "Temporary Swift feature flag profile" + required: false + type: choice + default: default + options: + - default + - all-on + - all-off concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -74,11 +94,30 @@ jobs: PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN: ${{ secrets.PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN }} run: bun install --frozen-lockfile + - name: Resolve Swift visual target + run: | + target="${{ inputs.api_environment || 'local' }}" + case "$target" in + local) + echo "PACKRAT_ENV=local" >> "$GITHUB_ENV" + echo "E2E_API_BASE_URL=" >> "$GITHUB_ENV" + ;; + dev) + echo "PACKRAT_ENV=dev" >> "$GITHUB_ENV" + echo "E2E_API_BASE_URL=https://packrat-api-dev.orange-frost-d665.workers.dev" >> "$GITHUB_ENV" + ;; + production) + echo "PACKRAT_ENV=production" >> "$GITHUB_ENV" + echo "E2E_API_BASE_URL=https://packrat-api.orange-frost-d665.workers.dev" >> "$GITHUB_ENV" + ;; + esac + echo "PACKRAT_SWIFT_FEATURE_FLAG_PROFILE=${{ inputs.feature_flag_profile || 'default' }}" >> "$GITHUB_ENV" + - name: Generate Xcode project run: bun swift - name: Boot iOS simulator - if: ${{ (github.event.inputs.platform || 'both') != 'macos' }} + if: ${{ (github.event.inputs.platform || 'all') != 'macos' }} run: | DEVICE_ID=$(xcrun simctl create Swift-Visual "iPhone 17" \ "$(xcrun simctl list runtimes -j | jq -r '.runtimes[] | select(.identifier | contains("iOS-")) | .identifier' | sort -V | tail -1)") @@ -86,7 +125,7 @@ jobs: xcrun simctl bootstatus "$DEVICE_ID" - name: Boot iPad simulator - if: ${{ (github.event.inputs.platform || 'both') == 'ipad' || (github.event.inputs.platform || 'both') == 'both' }} + if: ${{ (github.event.inputs.platform || 'all') == 'ipad' || (github.event.inputs.platform || 'all') == 'all' || (github.event.inputs.platform || 'all') == 'both' }} run: | DEVICE_ID=$(xcrun simctl list devices available -j \ | jq -r '[.devices[][] | select(.isAvailable == true and (.name | contains("iPad")))] | .[0].udid') @@ -94,7 +133,7 @@ jobs: xcrun simctl bootstatus "$DEVICE_ID" - name: Check macOS Automation Mode - if: ${{ (github.event.inputs.platform || 'both') == 'macos' || (github.event.inputs.platform || 'both') == 'both' }} + if: ${{ (github.event.inputs.platform || 'all') == 'macos' || (github.event.inputs.platform || 'all') == 'all' || (github.event.inputs.platform || 'all') == 'both' }} run: automationmodetool help - name: Capture Swift visual screenshots @@ -105,7 +144,7 @@ jobs: E2E_TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }} PACKRAT_VISUAL_XCODEBUILD_TIMEOUT_MS: "3600000" run: | - PLATFORM="${{ github.event.inputs.platform || 'both' }}" + PLATFORM="${{ github.event.inputs.platform || 'all' }}" bun swift:screenshots --platform "$PLATFORM" --out "$SCREENSHOT_ARTIFACT_DIR" - name: Upload contact sheets diff --git a/apps/swift/scripts/generate-swift-config.ts b/apps/swift/scripts/generate-swift-config.ts index b8e3ae7844..1189a1ce0f 100644 --- a/apps/swift/scripts/generate-swift-config.ts +++ b/apps/swift/scripts/generate-swift-config.ts @@ -31,10 +31,60 @@ const outputs = [ }, ]; +type FeatureFlags = typeof APP_CONFIG.featureFlags; +type FeatureFlagName = keyof FeatureFlags; + +function parseFeatureFlagOverrides(): Partial> { + const raw = process.env.PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES; + if (!raw?.trim()) return {}; + + const parsed = JSON.parse(raw) as Record; + const knownFlags = new Set(Object.keys(APP_CONFIG.featureFlags)); + const overrides: Partial> = {}; + + for (const [key, value] of Object.entries(parsed)) { + if (!knownFlags.has(key)) { + throw new Error(`Unknown PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES key: ${key}`); + } + if (typeof value !== 'boolean') { + throw new Error(`PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES.${key} must be a boolean`); + } + overrides[key as FeatureFlagName] = value; + } + + return overrides; +} + +function featureFlagsForProfile(): FeatureFlags { + const profile = process.env.PACKRAT_SWIFT_FEATURE_FLAG_PROFILE ?? 'default'; + const entries = Object.entries(APP_CONFIG.featureFlags) as [FeatureFlagName, boolean][]; + const baseFlags = + profile === 'default' + ? Object.fromEntries(entries) + : profile === 'all-on' + ? Object.fromEntries(entries.map(([key]) => [key, true])) + : profile === 'all-off' + ? Object.fromEntries(entries.map(([key]) => [key, false])) + : undefined; + + if (!baseFlags) { + throw new Error( + `Unsupported PACKRAT_SWIFT_FEATURE_FLAG_PROFILE "${profile}". Use default, all-on, or all-off.`, + ); + } + + return { + ...baseFlags, + ...parseFeatureFlagOverrides(), + } as FeatureFlags; +} + +const featureFlags = featureFlagsForProfile(); + for (const output of outputs) { const rendered = renderSwiftFeatureFlags({ enumName: output.enumName, - featureFlags: APP_CONFIG.featureFlags, + featureFlags, sourceDescription, }); writeFileSync(output.path, rendered, 'utf8'); From e6b178fd3cea211959290da14347ebceaa83f83d Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 07:03:45 -0600 Subject: [PATCH 11/81] =?UTF-8?q?=E2=9C=A8=20Polish=20Swift=20beta=20app?= =?UTF-8?q?=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppIcon-watch-quicklook-44@2x.png | Bin 8402 -> 0 bytes .../AppIcon-watch-quicklook-50@2x.png | Bin 10418 -> 0 bytes .../AppIcon.appiconset/Contents.json | 22 -- apps/swift/Resources/Info-watchOS.plist | 20 +- .../WatchAssets.xcassets/Contents.json | 6 + .../AppIcon-watch-launcher-40@2x.png | Bin 0 -> 7943 bytes .../AppIcon-watch-launcher-44@2x.png} | Bin .../AppIcon-watch-launcher-50@2x.png} | Bin .../AppIcon-watch-marketing-1024.png | Bin .../AppIcon-watch-notification-24@2x.png | Bin 0 -> 4146 bytes .../AppIcon-watch-notification-27.5@2x.png | Bin 0 -> 4882 bytes .../AppIcon-watch-quicklook-108@2x.png | Bin 0 -> 34858 bytes .../AppIcon-watch-quicklook-86@2x.png | Bin 0 -> 23984 bytes .../AppIcon-watch-quicklook-98@2x.png | Bin 0 -> 29523 bytes .../AppIcon-watch-settings-29@2x.png | Bin 0 -> 5300 bytes .../AppIcon-watch-settings-29@3x.png | Bin 0 -> 8512 bytes .../WatchAppIcon.appiconset/Contents.json | 92 +++++ .../Catalog/CatalogItemDetailView.swift | 7 +- .../Features/Catalog/CatalogView.swift | 64 +++- .../PackRat/Features/Chat/ChatView.swift | 11 +- .../PackRat/Features/Chat/ChatViewModel.swift | 18 +- .../Features/OfflineAI/FeatureFlag.swift | 6 +- .../Features/OfflineAI/OfflineAIView.swift | 11 +- .../PackTemplates/PackTemplateFormView.swift | 1 + .../PackTemplates/PackTemplatesView.swift | 75 +++-- .../Features/Packs/PackItemDetailView.swift | 116 ++++++- .../SeasonSuggestionsView.swift | 9 +- .../TrailConditions/TrailConditionsView.swift | 32 +- .../Features/Weather/WeatherView.swift | 135 ++++---- .../PackRat/Navigation/AppNavigation.swift | 13 +- .../Sources/PackRat/Network/AuthManager.swift | 7 +- .../PackRat/Services/CatalogService.swift | 5 +- .../PackRat/Shared/FormSheetSizing.swift | 16 +- .../PackRat/Shared/VisualSampleData.swift | 57 ++++ .../PackRatWatch/PackRatWatchApp.swift | 14 +- .../MacHomeFeatureTests.swift | 56 --- .../MacNavigationTests.swift | 24 -- .../PackRatMacUITests/MacPackTripTests.swift | 77 ----- .../MacSecondaryFeatureTests.swift | 116 ------- .../PackRatMacUITests/MacSmokeTests.swift | 52 --- .../PackRatMacUITests/MacUITestCase.swift | 164 --------- .../PackRatMacUITests/MacWeatherTests.swift | 49 --- .../Tests/PackRatTests/OfflineAITests.swift | 17 + .../Tests/PackRatUITests/AppUITestCase.swift | 85 ++++- .../Tests/PackRatUITests/AuthTests.swift | 14 + .../Tests/PackRatUITests/ChatMacOSTests.swift | 7 +- .../Tests/PackRatUITests/ChatTests.swift | 7 +- .../Tests/PackRatUITests/HomeTileTests.swift | 82 +++-- .../Tests/PackRatUITests/PackMacOSTests.swift | 2 +- .../PackSubFlowMacOSTests.swift | 2 +- .../PackRatUITests/PackTemplateTests.swift | 24 +- .../Tests/PackRatUITests/PackTests.swift | 36 +- .../PackRatUITests/ScreenshotSmokeTests.swift | 27 +- .../SeasonSuggestionsMacOSTests.swift | 29 ++ .../SeasonSuggestionsTests.swift | 22 ++ .../VisualScreenshotTests.swift | 125 ++++++- apps/swift/project.yml | 48 +-- .../scripts/capture-visual-screenshots.ts | 318 +++++++++++++++--- apps/swift/scripts/lib/e2e-api.ts | 114 +++++++ apps/swift/scripts/run-e2e-macos.ts | 85 ++--- apps/swift/scripts/run-e2e.ts | 91 ++--- .../scripts/validate-app-store-assets.ts | 7 +- docs/testing.md | 8 +- package.json | 2 +- 64 files changed, 1474 insertions(+), 953 deletions(-) delete mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-44@2x.png delete mode 100644 apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-50@2x.png create mode 100644 apps/swift/Resources/WatchAssets.xcassets/Contents.json create mode 100644 apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-launcher-40@2x.png rename apps/swift/Resources/{Assets.xcassets/AppIcon.appiconset/AppIcon-watch-longlook-44x44@2x.png => WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-launcher-44@2x.png} (100%) rename apps/swift/Resources/{Assets.xcassets/AppIcon.appiconset/AppIcon-watch-longlook-50x50@2x.png => WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-launcher-50@2x.png} (100%) rename apps/swift/Resources/{Assets.xcassets/AppIcon.appiconset => WatchAssets.xcassets/WatchAppIcon.appiconset}/AppIcon-watch-marketing-1024.png (100%) create mode 100644 apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-notification-24@2x.png create mode 100644 apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-notification-27.5@2x.png create mode 100644 apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-quicklook-108@2x.png create mode 100644 apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-quicklook-86@2x.png create mode 100644 apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-quicklook-98@2x.png create mode 100644 apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-settings-29@2x.png create mode 100644 apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-settings-29@3x.png create mode 100644 apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/Contents.json delete mode 100644 apps/swift/Tests/PackRatMacUITests/MacHomeFeatureTests.swift delete mode 100644 apps/swift/Tests/PackRatMacUITests/MacNavigationTests.swift delete mode 100644 apps/swift/Tests/PackRatMacUITests/MacPackTripTests.swift delete mode 100644 apps/swift/Tests/PackRatMacUITests/MacSecondaryFeatureTests.swift delete mode 100644 apps/swift/Tests/PackRatMacUITests/MacSmokeTests.swift delete mode 100644 apps/swift/Tests/PackRatMacUITests/MacUITestCase.swift delete mode 100644 apps/swift/Tests/PackRatMacUITests/MacWeatherTests.swift create mode 100644 apps/swift/scripts/lib/e2e-api.ts diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-44@2x.png b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-watch-quicklook-44@2x.png deleted file mode 100644 index 18d0ccfe20858b5a95cc56e62de502cc53901ece..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8402 zcmZ`;WmuHm)*ht<6bT7oXq9FN5g1@lT0y$I1{k`#Vdzj=Qer^5yAh-tX^`&j<^#|B zy>*@QT-Tnxo_no()n5DgGl9~QqE9f0F#rI-llNjmvUj`k{d)B9?)T<2OYd$&HhlN- z9RN@qj(Mena`#T5Cnoz50C0W<0C@TW02gyya7J&2{zYF_-6>uLM^==tF;Q1}zr)TO9 z10esl0RZgp_WMWsZr%I(y$kA?+v@;6Kpmi_6!K6g0D$!M!5?1lY`#AHldb#}=`ZkiA z--%{WTOA5VGgEULup=MUZwv69d=G=DD1MvRn($F6f0U*Ww6KO!ykTZxW}$*$P*70t zTI(BtWralkpx?dmQ5o6VT7p3!2L}gc2X*AFO-L|D-`3VgG}6ulb)eeZ4<3w6wD}{oM$CJrL9sY6dm8wYlT5 zg8r=kt~}nmlK=}^Lv?H|tmQ2%Od-Fgffw{|_^)p8-lq+gh1yt{+TAxz!5n4_VdDk; zH~8Ny<-ceU?l*sM{to_|pz_}ce+U0YxbF*C8s-Q!RThHXRcmvX9E6pH?KLmxzpDOC z6|^w5u$Hsb(St%*|DgO0`nUD(Kq~(`(BD*lqw4E{_5RcB--G<;eEptiIjGe?(fjkc zA4yXf<_Z}guIctQU)8t)xy3$rza{xjC^K<`X#|B3#U!TntWo9dVw z@=-Z5=|c^4>`ZN`AksQ|CbBxV6igI%r}@7V`={UEiTxvm|8W0y6Mtv%AN1~?Lon_J z^8ddX5RAwA2fF|O3HZIxJ9)UEOK*zr=cOJaH1@{xFLHQWoo8UI&D0G}?B#lC_L9hPvBH;!nh z_=vpg5z2n7y_jV5do?4hf+}5*8Sjm^XIif7q-=WeA}z_N7YmNFxfzL8=7ID@S8mVh<~oAZ&dArUp)t-Gqu2O3ISMN>LRZ1k0sObf8u4o#E^9uUsL$r9ZE{ zge@Zdwe_^}8}zfdaPgQN-Y2v|)*@VWSBhBji$9*zMrr7EJd;A17b(s^>x!Tj0~+H* zvKB|PqMn(b8M`dJy554D`C|Zs+32;h--k9YGfa@;dr2r_jpqP~5ohUYCR;>mAeW$r zQ9b=5eO`fPV)TUM%ZPIm(1++EgYuUX*;YZEs;%9Gh#+n5msNZz#Zru0F`r?ITjov^ zZ@MtcJ&WXV_Rj-ilX0BoCj^BJ=#6@~*i_T3c@ukr>yC_YI8jNb9twVPp^Fz9F|rN+ zY4KivaX6@oQKzu_%i7EoeR?aW8c#_izi_}VR9L#kg5s8ev;)XT%L^Z7$>C?U@4wT+O*4&fK6VTNP9mrucWac*hG&&hp z;q-N(BB&qcn0?mqj5v&uwpZP4Fat4cbHElMDnebpE$C4IIX|jc+41@u^)+!(=9m?O zBta0~`l#9+81l?Owz+#O7CTY|Ch^ zdb`pEV!H8BK^0KU;ru}qjnj}BO;ySTf#Tji%Cm$U#Q9T62t@ylWq}v9rFAc*^^MAmd^n^wOz||QrJJ5 z0=d$>M-qMjAE_Th1$y!%lJuu`0@J-!V9a;38MqbfCz?J?lX1O+iND^tqpA%USu%J* zXn?q}QnTSA?)~_*k5;TOE{fwNi1ahhrN5|1`nxV22Il9a+sa&KA}cwD8h&y{IKy2! zs(2|}g9t6lxHI6?RxlCKYIHPSG3~(YSxN$d)YffFUEspww$*nIsxHwTx;mgi9GcW4kF}2XR%qiDQAtU->A7r^|Y6)!-2V|@>nj~(%0irxl%!t_2%YQX$kBC zm+@9GwR2s&rpggdkCrOdE-!($TV$l0%)Vx^*We%|v)5qUungoYuNN4FHeP-%2@^F= zl)=N|1rRecqPihrR@QXt{BNE~1;>80Vz!#wOychd+K6$U6On~V9Y&p~t0;aV7K>Mj z+t1?RFL9n2LH|OBl)9&F^sHSw(eD$g00l1TW4NeD(V8rN0ET%PDi2FqTSUNoIz7$4 zE4xW^?GVuud29)z_c$p}gFTj)Os}|K{;&$kF<8m}mRPpZwT_t;(rEr}-w;Hf#LrE` zz{CaqU}(f8*c=o@eN;6v_taeF(`Vvu*}GWSAIK;Xk3tC?xUpaH7Y+M_r^Zx>u;{*& z;hj_3>XqI>_X^hSD|FffKN{QLCH za^???*kn=W)uYH5V4Nxomd6WY(O-LAqO(&~w;2$ra;pe9oS+d-sG6wD@Je4>EpBwQ zqFZ)LuTC2AX>RxslfU*`QC>jDE^Pg>cQmF@UD0_yD)Icb{<)|$kb3?!{BorHq_Hd= z#w8KU9wkh@IvBxrkymP|A`Ca&bCor$)R=5SpOD zwKKcSz9QN4tkh=6oO|S^#pO7TF<_-B&vN7Qk2pQ%;3rxpo?fY;CQ~>J6$Fp9)IYe| z%M5PhL^!%CDnzThxHnsJMZ_ff;=7BQjScUPY@6)_8+xE#UklFHj%@>hWC(7Y;L&Up za^`9*N8wY&2h=p8ABbwgn*bf$mQ2WyDrN!K#8*8XQXXq~?E6ZDdn79paU7Yzuw+bCJeIxHhR!8_)<)t# zB!^21Y8I5YRB)bliDBmP);01{u{_?IHngAvb!3#Ti2j1{4!yB76TXQHV13NVoIE~> z2c85Mng6^BoX~s3Y4Dbn%Q|g$=hxQNW`zjjlQ-6UD=TN+70tqqPD;L{m`=K`ESN#1 za_;VnrZNw*DysBv`oll+;17IE66CKvznV?8v>6*%pySS3h!{s*m6U2!yBt17OW;8T zmSc;eeA#_A+}WK=l}Jzwse&H(Y#R2&ePYJeni}&WL37HodBZ)5Gq^>Qw;jR>dvGz4cdXr&7g)BK2$pnZII%_Xe z40%gtOu9xybnbYLjPug!vfE+PV_(rswP@?&{6s}WH+H<9eAL11hDI$6xJyEWr6*R@ z+Hvm9*V3n2q=;;QYQL|@muEGOsbqo=CtZP8mWLO(b_ccdIbJ_*=H$Y4+SFd>N13<2s5_vxa<3dO4&_e`!p?b9-OMyrr73naF-vDyXeX7)z)W zTrVhQIT+?=$mgbLG5t`%jw#NoY>W8oJx@ z#8VGTy@|9=Lzkk~B--j4G}Y5O=__Fr2a<5oT#BtTHjXz1j^Ok~)cMWJsojh2)Qd36 z$B)R~2G9mh@ucKt$0yesuX9(SeU8TIk-)4;d8~{41TuQYu(aPtTqZ03RQ+9*6>b)R zZd&?~_xqc2i=PVlZXP|g2Wfqg3B(noo9Zrd!n(zhR=Z~*QJAm7+dp>~Xsvuzsb2Lq z@?B#N>%u&7Mwv(9ZAj{){O}#GeLE&uI_Nm#B@SGW=A!ZZvi_GEYb6E4%g;0GTeZgr zFaotgtCGPQBJOgQ^G%Ev;jg7skIT2J#)qzHQ^hh;ZD?6<<_9}S8}l2!zP`-tdLPIn zWwL7*EDovoN|YtL1x+7vWlS93UVxs^GG{7D2-Eff+jD>#{J5lAx2iMoaZQtB;Wus; zn+_Ink+zZ+KVP9ml4&Za#K%Ge#tMb7_!UDt-i-1*Rr^Xaz61j5++yu&V8(dt> zICq}8ZEviX_OJ7Cc{iu51D>e%nk za9T;4oGDK_%yL%GQj{PI?+$%Zo3O6#C;HRPU|wD*+=@{&o8nRxDW|A#C~%`m1G8qM z2q`>Ul)53MPT_Jh^a&TVHS`dVPms@t`qsU-M_6ucT~6{sg>StzJ>J8`*u_Q4=yJY- zr!a{}Pd7%q+rr&?mSLB32RsxesH~3f3+JHO`b@A*`*jFYZZWuLEay>vVg*ZnzF8QS z__u__C|_~U0xz7pM$bxaOP8DNYcrCQ@`8D^NPUmYl%#erxyK@lnEC9li|N|(!d>H8 zx4hDF7Z$#kaS2}*PA`is4G?HrMCvxoIx;eSz4oKP3hQU7m=G3cn~~cYDFy z76DWz?Rd#oMcY!B6mOvs%@WTJn{@Fj0ty(ZXB}fibYjm-i{7x0m5B z2pbo0R@r7jOuWCdkCK-!O+J(0EX@(tql)8?7#E(|RRP=C3Hv`4b*LOFq+0l)qEhUl z!-Mqktt9zjPYCum2ko=s*poNqr{p}dE`!tFl)U;YTO$us%f_u!f4b;7tq09<(FesQ z(Y#{7o6>v4g0K>P4dJVHyrR>Hd!>lBE4NpKOJ9015SY%^H80NZe0t~2O|Px)rOkoW zaL16qe(Zjw6RV;c=to{TDo-_eYMCK_EJrgwlLA&4Y@Iap%+K#TAT+o*V!8}={v19* zH?e+I@lH2QZ+n{rNJ*)lo^v(_CaTQ8tw5YusBMfE6toOt{3uR`(y#T3_qFFae3Q2g zE@fHmvZdVsKJW0^Zx7~8QIRbv$+4YZO7z~6AK897Moz%jt_%Kgb29r$4JAz6^f~1Z zJZsm@PO~YGnuXC^L;jls%{tn2C1feBd=%H}=`+H^7Jj*+g%7r+(WK6yR^PhmqgFEc zoTgf!Hix++HCodX)^m>14Pqg%!b0nQFx`BNVl^rwAA0w9m<89yEvWhe^Mt7 z7mDd+h*C&2N^HwI5d!_nsWd=C-XD30w`Ay%TfLm~<<`z*d7Iyr=PhT|j1f9~fwr#7 z2Q7X=$KlF`ZS?JkT{b|A5o0V-tIAe!mbCF^PD+i$s?XC0y{UXE{Us7N53VG>G1o)Iy*JvIX~+pw zzl=sW8ntk8REMe8^*nm^Gl!#ZF7HvGiK=6cClOa|ibj;vl0Ls**uaqg0;;P!qjeGz z^^o0!!j)p&7yW`L>%ML* z2~r~b_X9B2T3#6QPB{E3teAG#jCkHQp;K3z#x$X+SUdjN0AK7)J8|lEYpgB)jUPr) zvv_FLv2SUnQf|1QSPn829mVRPHY+v3GO-N}t>spwe5vFxcqtk;Cz-4WS-jdy z?EG0Ky*N$OZ7SS85&kHKf$Epd;uXL+1c1$!%V9g_`i=hj=wfIl+^nv`c=AcCWXfwD zpi=DtE`eslnjjXj%zG+&X`$gsA?!Aj23yqgGc(N9XakSyFUE}6Y~`(EL34-Ou%=<- zFlT$`Ia(GERc5{pY<%p9^MQc2g^bPOjC_vv6xGJlP8Z0<)pa-f8Vq|;8?z+EBgNHAJ z7INS(sgFwHHS#;w$e8+`kQ6Z!rBvvf%Qzcbb|=R~l5*{w9lJX0rRglh1xS#n{tl1!7@h;Dw#r*``A2jc)S*GzFv$~_c-{{k z9=cmN6_5LPkBN7(`MY_UBGVqoKD62}o6%N1uut{H`NO)|PXT0uau45EG9^fcv8Lps zO}U@x_AXsny>xnB)rhM2ZF?o`rv6x?Hr#%s@ot+OH{xhqvE$_mxf{4*d$y zNE<64Q*ZyYM;;P~3U}6CwS|YR0eZcrXYf(2drdJ!1Vuo6apODREb)Y7kZH8 zwv6v^U4*iQEg4qlJZ08N`2rj3jMjf_M?=I%+tqS@-~XeX1Alpav7ILYvt|l64-8#x zuYKBHflJXV8~&yLs$GGwk7-(M?0jIC-;?SzPuoHuv4m z#(KGz*q>CXS94eOQ(ljdZfe1+WkhW3kaHNCbV{wQs+A@<>=}|EwXJ#k7e*#|eN?Fs zJ9|^Ac>O4X=X3C56chTcR1Zt!@bl@Dmi7VS3G5N`Cy9GMf#}uH_l?pxDc=jn*G3kD zj6_!5a0%&cPvAFan%6g(=DgK?-4bm&U#(YsyBUIRN)Qh=`N{DDDEDnw*bX;BRNcgR z`rvp>IUoaQMN4~yt&aU9PsvnF28q-Y=o2168t)z=UDXOsl}^TSyRt=iuU(Z^nWZY( zXh#J4^H8(vgwrI6AH&9-#8N0Ea#zP4-42h-f(X@9HGud{VM9Fhy|Q33kC{Tl*w5&C z-Uyt^tQ}X^&4bHd)ApZcv%aXs`$UJxdm_q0&_koeslIj85CHYG^;zqGNP8WqsPwRw ze)<-o3y;Y}71k#=GMKj?e~#Z=6U3*IBSB0)`lhbhstR#>N)oPvGgH!dEG<`Q7lbGg zW1caFWEFTvpN@>8^kTipH{;}?T9CRO3T`3qc>QX$bFM#^niOHnNXRg$HyJoClo zGReqV7~d?RvFRqeT5i`%c5!A!osBdiLpMA%;djZh2F|j3pTZPllv?7Wjj+U(?2%HD*gvw`@wWsmWTV&=87R3zW^vR{h`+Qf+ctXY&Mn0i7mZaMt*T=U%W9;Loc)3dkaR~ zL82_#=?UnR$Z?X|>u9Q)mg!epSnoWqmbR!dW0YTg!qdFR`YRR5IBXR~5#(HR?PaZR za{3Kky3l!q5G0B-B2dKNgcQp+bYS%qbF3-ggdja2H7djCd8^mL=}++Zd)tR+&ph9H zk`H>HVKxwfQq#*znc8O8;VeZ_qw$U45?KEfxWpz?^p-p@{;*nq=wmt+K+}+(d5d7fo?yd(yI5-3dZo%C(xCer3g1ftW2m}f4_DJr$xifd& z+iO+V?yvT@rD|9G=0N@t{0NlMmetQ6bD;of?Zvp^-QUL%whs+jLftP_Cu#T*y zq9TCtMg9W-2So_@WdZ7?X+aVFB^yD}1Acje0=&3|V*hmqz`Zm}DE{B_ukaut)&Q8_ zIsm}Cm;Tpf^3s0U`rV70+PyOc$bsF!5ONJL7yy9If%?D8AG!9hmX82~`-`PHD3m{5sca@Vrf&~edGQ~;UV+p?Nk*qeb_-EAFyu>b<@pcl~= z>|#ppZVRz<2DuAT{?-7!$iHA9CHZd^7aKuJ9Yqy#aeF5)IX5dCD;uQ{A~`v^fRlwK zNKHcOPx{M~Amtku7Y7g!=;r3e>c+up?_>pJ=i}o8vb_ere$Dct!Q$*;=VI#4V&_ct zFC+hNM*{3@?qu!YVr_3n{>!eZnZ2uvASLB5M}J*^#%b&Dmm@ppKeKwt59n^{0Ay!n z1O7LXi?!vy!}|y8*U0~*f!(eD2kqC$|D;)%{}rKws}tn+L|B*u!4R-5*v`fIg~ty3 zEB}}D1YTAGB<=(@b+LESu(yW@{jLT9;D5sZo(+LtVT07b&h`-3Uz4V3XYC^NS^)Up z;QwUl{F^4k$NeYg@8EwDbpIRS@8I7Ezvcy`V(kuw=tx+~8PxRON z{3=O^wa{PNQvmoU@^7oZ)PAG?G#~){uigY+P#0?#2>74AemfF{xcn3ScLcxo8VF)) zXC+AK&SC+!GTs-z=H4D?)wesQMKjsqNW#PDD(bv~ks+tHP?j;e`ZoG} z|J8X+@cL^8Iz<+dz;T=N)B2V0I^XpQ4LiP(D&Ze+(3A9!NH76VUTUypCGaI_%Y|`Y z<(Wibp#8*Nr!VLBM_>!cP4_b9i_%*VRY)kPBQ5q}(6j~4xZ2^R7E=>Um6E8`1l25~ zh=jo>VT1+m=&F^A$+IC-oJEpsGtRp_?B(NYG%7zV!E~A^o7)zsR&q{<+~jUiP((0% z2$S@88bm3ObN-YV%4it>_B;^e(HqY!^WnnOQ>H9ZTMDNYrs+zU5L_eUf7OkF9gVUy zkc!)j8kK789B}#`I`k%&cNX<*qLg);K2TN!PD@NxH30!F`?mgrTru=E43Hg3s@I+p z^V#;ROzh|)m>hDu%P#9KOb}1jVaRdVfKsZS!ts79+{wFR;3#!54Js~=$o&>$ep66h z*1%487HkFzaupjDzw+L5WBWQDwy`W0fvWpb*3Xw!0UyHt_y4ICAmY8sn=?~ zSk0*#Px@+65?^0x%hPwFh$?G%f}C0khR+=Ggl2ND_~Mzd109Kq&>S+cC+W)B zBadyDaFOux0*yEd>sv{{39Qity@iaH7`!csT;4i3y0Z#=qr^E(Xn` z?A3=R(`ztWqG0eWKrdN^FJiR3_;o#ML|%q*(da4KMkRVkt_)r>|B(WNBv z%6z2{Wnp#QuhVqBya?HPMfa^pVN_988PVo2fkxd*qQ)M+Vq$mV9>ZuUF*T+8DHcSEyv&2cpZv~head)v znfx?VG!zmoul(1Z_U4A>o(;S=s))+AW8Wm2Gh*~aI(_(DqdjG3Q%{V+Cvw%pq%Hd? z@-+ELp&G*Wv>whvK=uXH?p5D<3NhMzG`x5{JG<&ToaWK~*q8Xt-C@gFp#Ey&sOs?W z$3e{Y<#dzWFcvB{H|!yx-l=%CFn>agwEz$8t4K{ZApvEl%A3>IU`-wZA`x?dKOt{% z5BCpOU?pck1$P5Ahsqyb+HUJp9{JB+vllQ5T~N^;SW-oFA?vKA&=MMHPtt~c^_WoPt7TP*W~vUm-@1nGe{BaLO-4vu4d-?qgKb> zm=h>?x@oTv#{jn>(qB_cmnlruTKlCqzZFjbT-c*blm+6q?+F^A=3SYTjIvv2!`CvR zuabELD3mV1JLpR*rtmwJu({TuB7wjL3ha?Eix{U;bEt)5mi;r$F$&Yl_V&+H3DSW; zDs~lEh>+LqMdY`WiZ%=@rLjx_Mj8$-A!58pm&+tlJ5LYsS!WwmZt3@!+p{w>*^FOB zN}Bip>U%W9@zUJJk$A<}#+^6tjffz)V)PZ8dDoLA2Cp){5Rfjhwp0)zt%ede2PO%ulGp7)TkT3)sloUpvBM&!C2-I?pA&v+4 z;Qf5{7*yQCwpFr)`_q95s&?{8|NZ zKhuU)Lzl7fhNVQ4y*A@*RRYmB)e)IKIORw8>c)P7?y%0)t0-$S(Kyce!!uJFtS(*G z*#4!w>Wzz@&pRhiE8rYYwysdoAPHipWBhrbtg4Sg3PLjBWI!uw8}2@V{3n&IY^?^t z!bvz0KOIZeau>Z3Gtuw`l#S)q`+X`wtl9(ux3=D@WQBfIZXs;1Lw>rQ0-CR%zju@q zp(YW(k)mpNqq89C-}U;!ye^a2;w-V;A!MNd?M%T7j^*^)-ZYi3 z%CUH(jV4ZJJ9j}fj;W@?A2Zkt`TI`)cRX%Tt0&nPv!$cm?+HBJjuZIhqExQD%gMx$ zTNmv|D^J3Ank1#xKLn=Mre~)^JV4G~cSw!dPjlbPJdIhQ+EI-$Uj=!aQLt)Q{;+!` z7t2mlYI0RF!S?ooP(2yD&r)t)7V++*k+$n}+IB)0v|Y`@gbGpy!$6RDXLB&Yjob3A zIvWkqQj3i~h)A#Xv-Gms8fQ6qepzW?-Uf^V#3ATq?}Mt*lN(Xf$*wo#nV4qG5_Jm} zTm~B#;@RTW>8qcA9vsCW!FD3T;ZEf?ZPZONEF=oGlx@-NDpDss4^UAM^KerkJom_K zmdwzgdDnn_v1;T#Y3*&Ca!g{znBgHMm>pW z+pixwTBnSYa`%2`sh{5fptM_?1 zmRueeqNFlNgFM@xyBZ9+Y#X!m+1^S7s6EF#_}RpKH4KO8pQYcg`{3{no&S)VM@2DR zlZq!u49T}60@V#MogZdjsCa6mi=r^Y^QS}4Ae&Y=JTUv={2bA7pMw3Mqu~#t)Ah$M z#P@?L^f!~0ecC)^YnoWrkryVWqzTyrjWx{vo8^^jMy(H;^_8vP${A?uGL;1AX1TlV_CPHT;BaH*Bw}B)P6|@SmTK z*kL9_?fv3*{~$SCJ9xV(1Zue-Yqg(kS-s@+*;(qFnkG4au z=(&9oDcvk6G0Q&XBUxSl{Q0$&HQi~pbUT{TSz%S#N9A3AZ3cS8SNspSBwU&#b1F$A zQ@1pUn5kqs=xT9R5zX3eDN-m%Cb=FbTe5B}YDA|oSDcUgtB==)oYtWjn?LXKtQT#w zz7P#(;EfC!`uv!oKtv^(TbO$+*Oq&_StG4La7n=E1f7D29rIg<{bl)&HWn)r&w{OK zjgGpv*4QM=Z)Ed-8tfU=n1noZ^d>kS58{#(M9)4(_hKn$r1~$bSt*Z7`JlXdsxib5 zoafOHx>!Orpr+k!fjbKoqbD~c&IylbcS@z%Z96x?!QY5Z&jU5NX>r75P2tj_93l^07}TOwT(=5AJLFg?*rSTUj3U^bX|`W`37<=V8f#aeW2 z(#-L0K9TE-zClJDX0`yM>y!iYzOr}SabZ1t8R7D<4OI@Z6_J6v+(4qV(uoFE(V@T= zI;@mKpA3V$Q7t<60R`J`SrmGvVIEI-?@h>VWIH4|K>}!7dAoE`5hp92<@YJ;i(6Zr zx4g%B<8Dm%qZzk{L9cW9weNGxN2*arakys(XVvM>u>`wP8D>aCSv*0_I=2EspT3%9 z^&dFwR!~5_oF*(zx3188*31F8(i?~`5^gY`G3|HbuhpL7y3^AB;EV_e|j@ zdpc(2HV3-KlTNp?;!8}S1Isk^Ol!;c!^1^}?IrvRzR*`0hd8WnO_j6;nWLZh@f9HzT9LA~EZ-dSPIz+>c_c*#${J6KoT`vM>?{BUq}I1|)g)eFvlO}XK1kW4GH6(Y4Zt$1{XU*}5~v2oRq;{42o?~E?rUn=u-aki@>I0BUi%Pz-aaCE zobZwK#zA^DV*O)AHnDOgTE9Qh(drlPg0R)C1m0RI;$F616eS5_s(T96X89qsL7ewH z0jAP}BBBWz*ctpQEv=qAMh4GzAop*Xg9PH^F(jIw+`iUq$1y2q-h!224>wFC_0&?2 zDPvO@(PviJ6WYXc!i}XBv!FL~;e>-C%SOz&o|ck~Oz~cu5F$Kx-0$o@nhs015*2?h zswvIK)6uo_s=Tq`aI)idcE@!!cY;_t`6w}R4{lK>1@?6jr!_^Bwsuj~;vbY}L0T^3 zJsqG2Q}Gcim$bAe7Z|iL`i?~z(b^0@6d%cyi?a341xFNAjT1>XcGDx|bdw18ikern z?5Xb~?hRLTB0)^Oj{{j>1Fuk`g&WqucnXH^ zwM+~0LwW|4!`01_TB5t^ZrNz^D0jh84X7$5b@G}nstBBJa$!b9vaS;z+y{EdW)ZI26H#~6N>Iy7x!c%#+)|DYYSVBk|9U!hSy=iR5S<=cnP{I zV>Zd;DzEkevBJ&L0*nmi>?_|i-~9lV>9^F4SB}rN5;77lS>YA<9*=mne7r{!daaIS zbaSffjL31CUB({+DN?zP2AQ#^nWj<_z%hD}6C!@}5De}(gOh%+_832$PL_dl&qR?g zUtVJ@9hHj$jwo^C32KgJJ=gBA)vdWGL5*qQjg6618t!H9YYAkhf$R!^=T!0Ao!cF*1I=ogK1`Fn=-m%y(!&^!FA_Sr`>qn;lkv>@*xa!r2ME;c_&R_UmGhQ`IYTi zh7<)I4fHJKyj-2>UZe7y8?|0)oW5;R3UJ5T_)aHed?|TJl9KIk-Dn9ex)n&tGEDWj zPO7!{brQ4-8VJR-V(eScLC@36@@Ky-JjJa|)J6I_92py#69;@hmO%P5iY3WtGFrdE z-5Y8gs*&t1v?913W5oa-Y!BgES)6ve`&J=v;1rKddf?yse%P47|6*xsiHApPs|s;zgFM2Up_t*}kF^k{1j2s$n73s?iQtRqr+lgATNX}9WjDfC}OKwgCrCji1 zoVr(I88;yT0}iVq1fEoHSt4ln>7%b}TS`d|c;3h={gI?e()^r+S#dzXaJta>QOI@% zC8BsFEiw4uydY>IYFF9tYJso~UXV@wTckuM{!T;B))3}w4;phv2@YRpsy(nuFI$2B z$V};EDU^+7{iLP4>jxjzMHVoCY<+TRG9ru37-33YauaeNzx|H4T$0KnLu zC^c!rlg-6-aC74*tM2s;MEFBE{G;(@kKj~6!O8lZ)`evLM@YFS8Y@2D2>z&h6t+Oh zPlDpSiOJ^EK3qZPg6{0#v-2J@xzQ&zKC@qX-OD(Xo-4PO;8+Cbb)yLOwecS$_7A0bQP z_N26Js(Xf+a!tO_okZ9Q7nAHcdPpyLh5T8GJ^BmO$JgVX<>|}muF$U)I;rj4qlRU= z_RAGe`a>R3YtXHng4z%_gzPRnV|>D0S?C0RRzFVzb3fAsO0V#tQ8V6v+kIc%SM)5OtZ=dTiXUE7Y#y-4G)eBQk(6#X6>`R7( zq@ze1Xv6ZFu*k#glbvOm=M8m`{sve9NawUo`U&TY{2>s<7DU{Z=Fgx81%(7o9*mj4jE? z>urH2>eK z5c+tVHD!Z~Bj~bi9dYc1bt_X0$AmcJ>Jpu(YFjS$Sds>Ii8D@TI9Mr;+8pg`1*W|} zj{i|F-ICmHbT=8@a&~t$7fsqG{g@zMRDEm8ggPhbjzO`0xVSrn@g3>&Smm0p_gSqb znz7U4?ez1>UBisTVFv@Q;S?R&S!bbxBKqCFkAJ7fj*KglhZv*1j+_>}BpboY=cg6> zRufCs)Vrp-rxsybkP2Q{M7O!A5Be)?{ti?WaVXn+w8p_@wmvBQFt8l*w)O{MhRVC? zp{(I5e%GCrvog5%WYZCiW~wl%d&H#hw&%f$3?USz)`$?0f*}oFEG{y1XgR?H_j1i{ z11V&$!#1SH0iB;@v3-+bmMykGlT)(gdcI+bE9b~>*!L;V+~2-w^Yj3Zdvo6X=WVh2 z=j(ZeO?4_3O|hWq;A1@;R}Y`b!4oqNaa-Ou7ZOzijwDE;@7_NANZ^=H<~ppj%U@4o zQxc)ZFR>>X$I1%V*cQ^Nj5gYgncKrPlEjaRe2s4VLSHY_YaW33)2R52Q1~W6jQy%6)FN(WPc;>g+cgJITodCOldG zM0bv-4+s&;W8H>=H{25-n*6abM(s^yW8-H-WgqT*wuA5TNhqs+9Nj z0fBN!3dEo0^TbQdK4`326IchbLZ*PJT!cg1KW!ws%4_|!0!F%y&64J&KPdHY40Ns3 z;XUnSmU>WKQi;_R>;}!~Ld9^_$~tbsyD`ii^FAC`*?rk8X9(5Xe&C zlNvoqFnDdm5mST-e_%Ppb*~^18M%efA?~{xIot|e^*(Wb)=n3Axe@v< z5I1m%I(}!xnh(vG3rm|@xnk)#Lc75nxJ{r?9rC!}h2>24#g3H$e{Q>+E~=XkRlN^p zBvJ+YEFZeB7)Fw&vZP8+hm$+QXZ*A~3Z6(0GX&66c3x9U>ha$YONc3nG0zXDepJO7i) z%)S~p+2kU!evq3wFAk5dLJ|0>>R~jawLxo-=M<;UGWp46C|>={a(CMGlAR2v-WJPA z&CzWp9|{dV;%w?#vwmU+i6dAaL#M&9h>4gZud+Gc!d@_4&}$QBW@S!uefhNKpjVyF zf|~Bb8@|RsO;;UfGgF!i5}dCGBJT?`9KR7_K?o(08a_LAE@wZ#a=T>9d9vTCJz&QB z;=Wd0b>La9Pv&XUA5lJ}#5`aiV6~DtxVXSGLhtZo);H63k#z8_F_~e#+GpME(Yn#g zoYoR|ENQ&0_PpU)hh^7F68;_rA2&+rL256MjJ#N7SBFoBqK^@gPXIxvrR;S87ejn`9`pSE4fCa_gjzi9~s@S&TG_fl@o$LTg%BJ3qIvLR~qmJ zx1qr3QLd6f1pL}-F>^My69h=(!IIV=W?CpjQRGaF$HQk83-)d%W5@|5Ak8(3Q6!fvSvi1oS-wXq_yMA){-3;3nu&)}o>t?HLrfzQASPfCs>YOxMHdfF zdaX?;{qe9eQI5Y+hC~x58yt3l2&ez`*g)rxA_r6VZu5ihc;M|cSUZ?xtp>=_vO`HO z2`7akxrn}T`s9r${frrdhA3fug@B)zhli_fcU-j9u>mJ5zw4&Ku6A(v^&HUfX)QZ& z6j!}K&6>O_gfPNFmRTs(>wFoLI*FG#iWXY&o;*#m+N|b6T6DApew=AQ*8Au@Fh%f$ zU5i(Xpw}sagTXR0bt9Kb>f4GKS2*}HlSQhDMqPd_p8V6fu)derSG}`&NO|93d6`W& zvL*$aMq5B;mhM(;Y6J_4`eF1Cvss6L#SUmCYA1VQVvI(Gk$M=eUKM_r@>3;WIl4kF z_$(EJfa)_VygHV6Q(?sH5?k+EgR%`u8p853XK@93zKXrxdqURR#bujD&HU}GOPuvK zPq3?ZP%5T9{+l+;U=X5X3W(r8PbJ8ZO`(3dXtFPGCMii>jll$1V1G%vF>lk^Ow;6UJ?^}fg8<2)$H+gfKfn>JX zm*1 z>;x#x((c1MJ6t&THcQ`?hUAoUk+SLvZX%S%plyzAE4vIy#(-wH!t}b$skrm1R>B&x zxRI@P409a^rZ5VqOnrYK=~n0*Lj(`cquT1)syWvbp>FF0J7 z(Qd3Xm@0R#&yn=lhh=icttAJPV$oV`p!t1btSnkzJtF`(WuP8?&@9$^NL8>Xn1_NN zH}T?P=QkV!+(=Ml-pMk+Z%EPmWDhdaSsv8|)*86*F>-TeQ^N6_nyv`!du*Qv>U>tp zj1{6y@KGga;d|KX_vC}b=ov?+3S@bUTZV_ykUhwz7-cZv49A|_LG}(R>8QIgJg5$v zAP%)#*!PW10K*;ovWNZE{XjCvOoHHIE+Sb_)}+5JJ*N$%{Mp9sHN~i1Nc7 z3p5wK8$aX{rDOa+@~UJeS$Q;4v14AA>|_GXWW6-_mg|B*yQh-~ad$(4s_`=v&FygZ zeJ5;>p0B*aG$Z@7qhfLbTio-TIMj>2{$UiYl?oBhxbbY&Of3>jG!i4IYm<9eeC1{Y zA5S!VIte`$XtbMqg53Za74`f!j+H$hF}ZSO?IDAvpWDo5Oh+T;g?=cr+on)=eqm1& zJK|Q;qL9)rV$r3Hun4lQMEj78;kHapQK&l4`EszD-UW70IB8c28A-3^ajOMjV-3YW zZT;LDFk}k!9ia{;CI3`{P?i^(v?av3?K@1~Bs#fL%JA9tGwgnSlDlSa0pmusyYyR; zHVNzDcyOT{Wj`KC+zydwkZyNXyjaNGM&}y=9(k{LUFxoNuC7VeBf0!5ab^YrF<$p& zgjdM;&8YE;{D?6DcvXsnP7T^EV_z4I_*}!mC49Yi!HOdYEy`O_bD!oBKk(0JSlE|L zD6o%Vhw6^=qI^a^==?@27LU|GM2!`ZA_a~c6Qj-8NTfG`SE-LuGt4jv8N}{xwh$jm zE#n_k$EF@eRJ8P{m*;T^CoW7$ESsFBMHpNnE9k?N>C9i9fEM3(_P!wqdRBxEZ4BN?LvLv#eZ7vg2bHB+8 zn8E2h=NU~8r}#jHIlG0bzJkkt4`(9hB7#B^guxVOfoqaCT+=xJRhQN!zm zHkQ(1BVRCALpi#1j}K4$PG-n)Fu^S^)-}whRRd0TMOPM|35^*-L<{M~xRCK_rYDaw zQ%gK^v&B@zT@VAQeS>FGktzQfK{p$}8qnkBrWD3u;m0>+6o+B0dUdkNe@Y=+Q=r8M zVE=>Z>jL9zc^8H#z-Mf07~DdRW90Z5MtpzC;7J=3WJd}X28~`c{13kGkyq1U$Jt|f zAQ7<^Q>@K_goiqbfJEmQLeJz&O4wYSTFZHNCjDplP)7^SN;n3LUw_-lN-9ZIikSrb EAEFuSfB*mh diff --git a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index eae9d600e1..67fbfbde7f 100644 --- a/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/apps/swift/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -162,22 +162,6 @@ "size": "50x50", "subtype": "44mm" }, - { - "filename": "AppIcon-watch-longlook-44x44@2x.png", - "idiom": "watch", - "role": "longLook", - "scale": "2x", - "size": "44x44", - "subtype": "42mm" - }, - { - "filename": "AppIcon-watch-longlook-50x50@2x.png", - "idiom": "watch", - "role": "longLook", - "scale": "2x", - "size": "50x50", - "subtype": "44mm" - }, { "filename": "AppIcon-watch-quicklook-86@2x.png", "idiom": "watch", @@ -202,12 +186,6 @@ "size": "108x108", "subtype": "44mm" }, - { - "filename": "AppIcon-watch-marketing-1024.png", - "idiom": "watch-marketing", - "scale": "1x", - "size": "1024x1024" - }, { "filename": "AppIcon-mac-16.png", "idiom": "mac", diff --git a/apps/swift/Resources/Info-watchOS.plist b/apps/swift/Resources/Info-watchOS.plist index 6943a9d00c..dc5ea643af 100644 --- a/apps/swift/Resources/Info-watchOS.plist +++ b/apps/swift/Resources/Info-watchOS.plist @@ -8,26 +8,8 @@ PackRat Swift CFBundleExecutable $(EXECUTABLE_NAME) - CFBundleIconFiles - - AppIcon-watch-notification-24@2x.png - AppIcon-watch-notification-27.5@2x.png - AppIcon-watch-settings-29@2x.png - AppIcon-watch-settings-29@3x.png - AppIcon-watch-launcher-40@2x.png - AppIcon-watch-launcher-44@2x.png - AppIcon-watch-launcher-50@2x.png - AppIcon-watch-quicklook-44@2x.png - AppIcon-watch-quicklook-50@2x.png - AppIcon-watch-quicklook-86@2x.png - AppIcon-watch-quicklook-98@2x.png - AppIcon-watch-quicklook-108@2x.png - AppIcon-watch-longlook-44x44@2x.png - AppIcon-watch-longlook-50x50@2x.png - AppIcon-watch-marketing-1024.png - CFBundleIconName - AppIcon + WatchAppIcon CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion diff --git a/apps/swift/Resources/WatchAssets.xcassets/Contents.json b/apps/swift/Resources/WatchAssets.xcassets/Contents.json new file mode 100644 index 0000000000..74d6a722cf --- /dev/null +++ b/apps/swift/Resources/WatchAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-launcher-40@2x.png b/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-launcher-40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..684e64feeee408d9235a2beab21fd8433890da3d GIT binary patch literal 7943 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 Kt4hQ9%KreYur8bc literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-quicklook-108@2x.png b/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-quicklook-108@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..508a5293bb57db8c178c1ebc50c5b469f72576b8 GIT binary patch literal 34858 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�aKu$-7*bO*CGO}oWKh-RdW-YCgsLS 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 literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-settings-29@2x.png b/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-settings-29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6a4c2d88d626ddcda7bdb7032a9f63d459973772 GIT binary patch literal 5300 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 MuV(@))o~8_9}6SmPXGV_ literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-settings-29@3x.png b/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/AppIcon-watch-settings-29@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..64fcb30d1e1cd2728c3479296431578efce98376 GIT binary patch literal 8512 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 NE6YETE0Hk|`aeU$Dx&}Z literal 0 HcmV?d00001 diff --git a/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/Contents.json b/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..6f0354dab8 --- /dev/null +++ b/apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset/Contents.json @@ -0,0 +1,92 @@ +{ + "images": [ + { + "filename": "AppIcon-watch-notification-24@2x.png", + "idiom": "watch", + "role": "notificationCenter", + "scale": "2x", + "size": "24x24", + "subtype": "38mm" + }, + { + "filename": "AppIcon-watch-notification-27.5@2x.png", + "idiom": "watch", + "role": "notificationCenter", + "scale": "2x", + "size": "27.5x27.5", + "subtype": "42mm" + }, + { + "filename": "AppIcon-watch-settings-29@2x.png", + "idiom": "watch", + "role": "companionSettings", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-watch-settings-29@3x.png", + "idiom": "watch", + "role": "companionSettings", + "scale": "3x", + "size": "29x29" + }, + { + "filename": "AppIcon-watch-launcher-40@2x.png", + "idiom": "watch", + "role": "appLauncher", + "scale": "2x", + "size": "40x40", + "subtype": "38mm" + }, + { + "filename": "AppIcon-watch-launcher-44@2x.png", + "idiom": "watch", + "role": "appLauncher", + "scale": "2x", + "size": "44x44", + "subtype": "40mm" + }, + { + "filename": "AppIcon-watch-launcher-50@2x.png", + "idiom": "watch", + "role": "appLauncher", + "scale": "2x", + "size": "50x50", + "subtype": "44mm" + }, + { + "filename": "AppIcon-watch-quicklook-86@2x.png", + "idiom": "watch", + "role": "quickLook", + "scale": "2x", + "size": "86x86", + "subtype": "38mm" + }, + { + "filename": "AppIcon-watch-quicklook-98@2x.png", + "idiom": "watch", + "role": "quickLook", + "scale": "2x", + "size": "98x98", + "subtype": "42mm" + }, + { + "filename": "AppIcon-watch-quicklook-108@2x.png", + "idiom": "watch", + "role": "quickLook", + "scale": "2x", + "size": "108x108", + "subtype": "44mm" + }, + { + "filename": "AppIcon-watch-marketing-1024.png", + "idiom": "watch-marketing", + "scale": "1x", + "size": "1024x1024" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/apps/swift/Sources/PackRat/Features/Catalog/CatalogItemDetailView.swift b/apps/swift/Sources/PackRat/Features/Catalog/CatalogItemDetailView.swift index f561b43fa4..98f870caaa 100644 --- a/apps/swift/Sources/PackRat/Features/Catalog/CatalogItemDetailView.swift +++ b/apps/swift/Sources/PackRat/Features/Catalog/CatalogItemDetailView.swift @@ -49,15 +49,14 @@ struct CatalogItemDetailView: View { private var imageCarousel: some View { let images = item.images ?? [] if images.isEmpty { - Rectangle() - .fill(.fill.secondary) + CatalogArtwork(item: item, iconSize: 48) .frame(height: 260) - .overlay { Image(systemName: "photo").font(.largeTitle).foregroundStyle(.tertiary) } + .clipShape(Rectangle()) } else { TabView(selection: $selectedImageIndex) { ForEach(Array(images.enumerated()), id: \.offset) { index, url in RemoteImage(url: url, contentMode: .fit) { - Rectangle().fill(.fill.secondary) + CatalogArtwork(item: item, iconSize: 48) } .frame(height: 260) .tag(index) diff --git a/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift b/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift index 7fe468ba88..ce7f35b52c 100644 --- a/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift +++ b/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift @@ -3,12 +3,19 @@ import NukeUI struct CatalogView: View { @Environment(AppState.self) private var appState + @Environment(AuthManager.self) private var authManager var body: some View { @Bindable var vm = appState.catalogVM return Group { - if vm.isLoading && vm.items.isEmpty { + if !authManager.isAuthenticated { + GuestLimitedView( + "Catalog Requires an Account", + subtitle: "Gear search syncs with PackRat's catalog service. Local packs and trips still work in guest mode.", + systemImage: "magnifyingglass" + ) + } else if vm.isLoading && vm.items.isEmpty { ProgressView("Searching gear…").frame(maxWidth: .infinity, maxHeight: .infinity) } else if let error = vm.error { ErrorView(error, retry: { await vm.search(reset: true) }) @@ -42,8 +49,16 @@ struct CatalogView: View { #else .searchable(text: $vm.searchText, prompt: "Search tents, packs, sleeping bags…") #endif - .onChange(of: vm.searchText) { vm.onSearchTextChanged() } - .onSubmit(of: .search) { Task { await vm.search(reset: true) } } + .onChange(of: vm.searchText) { + if authManager.isAuthenticated { + vm.onSearchTextChanged() + } + } + .onSubmit(of: .search) { + if authManager.isAuthenticated { + Task { await vm.search(reset: true) } + } + } .toolbar { if vm.isLoading && !vm.items.isEmpty { ToolbarItem(placement: .secondaryAction) { @@ -98,9 +113,7 @@ struct CatalogItemRow: View { private var rowContent: some View { HStack(spacing: 12) { RemoteImage(url: item.primaryImage, contentMode: .fill, cornerRadius: 8) { - RoundedRectangle(cornerRadius: 8) - .fill(.fill.secondary) - .overlay { Image(systemName: "photo").foregroundStyle(.tertiary) } + CatalogArtwork(item: item) } .frame(width: 56, height: 56) @@ -162,6 +175,45 @@ struct CatalogItemRow: View { } } +struct CatalogArtwork: View { + let item: CatalogItem + var iconSize: CGFloat = 22 + + var body: some View { + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(tint.opacity(0.14)) + .overlay { + Image(systemName: symbol) + .font(.system(size: iconSize, weight: .semibold)) + .foregroundStyle(tint) + } + .accessibilityHidden(true) + } + + private var symbol: String { + let category = item.categories?.first?.lowercased() ?? "" + if category.contains("tent") || category.contains("shelter") { return "tent" } + if category.contains("pack") || category.contains("backpack") { return "backpack" } + if category.contains("sleep") || category.contains("bag") || category.contains("quilt") { return "moon.zzz" } + if category.contains("water") || category.contains("filter") || category.contains("hydration") { return "drop" } + if category.contains("cook") || category.contains("food") || category.contains("kitchen") { return "fork.knife" } + if category.contains("cloth") || category.contains("jacket") || category.contains("rain") { return "tshirt" } + if category.contains("light") || category.contains("lamp") { return "flashlight.on.fill" } + if category.contains("safety") || category.contains("first aid") { return "cross.case" } + return "mountain.2" + } + + private var tint: Color { + let category = item.categories?.first?.lowercased() ?? "" + if category.contains("water") || category.contains("filter") || category.contains("hydration") { return .cyan } + if category.contains("cook") || category.contains("food") || category.contains("kitchen") { return .orange } + if category.contains("safety") || category.contains("first aid") { return .red } + if category.contains("sleep") || category.contains("bag") || category.contains("quilt") { return .indigo } + if category.contains("cloth") || category.contains("jacket") || category.contains("rain") { return .teal } + return .blue + } +} + // MARK: - Add to Pack sheet struct AddCatalogItemToPackSheet: View { diff --git a/apps/swift/Sources/PackRat/Features/Chat/ChatView.swift b/apps/swift/Sources/PackRat/Features/Chat/ChatView.swift index 35a127084d..0f8e7cd76e 100644 --- a/apps/swift/Sources/PackRat/Features/Chat/ChatView.swift +++ b/apps/swift/Sources/PackRat/Features/Chat/ChatView.swift @@ -30,9 +30,11 @@ struct ChatView: View { } .navigationTitle("AI Assistant") .toolbar { - ToolbarItem(placement: .automatic) { - Button("Clear", systemImage: "trash") { viewModel.clearHistory() } - .disabled(!authManager.isAuthenticated || viewModel.messages.count <= 1) + if authManager.isAuthenticated { + ToolbarItem(placement: .automatic) { + Button("Clear", systemImage: "trash") { viewModel.clearHistory() } + .disabled(viewModel.messages.count <= 1) + } } } } @@ -194,6 +196,9 @@ struct MessageBubble: View { insertion: .move(edge: isUser ? .trailing : .leading).combined(with: .opacity), removal: .opacity )) + .accessibilityElement(children: .ignore) + .accessibilityLabel(message.content.isEmpty ? (isUser ? "User message" : "Assistant is typing") : message.content) + .accessibilityIdentifier(isUser ? "chat_message_user" : "chat_message_assistant") } @ViewBuilder diff --git a/apps/swift/Sources/PackRat/Features/Chat/ChatViewModel.swift b/apps/swift/Sources/PackRat/Features/Chat/ChatViewModel.swift index dab5612dbf..79ad33b99c 100644 --- a/apps/swift/Sources/PackRat/Features/Chat/ChatViewModel.swift +++ b/apps/swift/Sources/PackRat/Features/Chat/ChatViewModel.swift @@ -91,27 +91,35 @@ final class ChatViewModel { private func appendToPlaceholder(id: UUID, text: String) { guard let idx = messages.firstIndex(where: { $0.id == id }) else { return } - messages[idx].content += text + var updated = messages[idx] + updated.content += text + messages[idx] = updated } private func addToolInvocation(to messageId: UUID, invocation: ToolInvocation) { guard let idx = messages.firstIndex(where: { $0.id == messageId }) else { return } - messages[idx].toolInvocations.append(invocation) + var updated = messages[idx] + updated.toolInvocations.append(invocation) + messages[idx] = updated } private func updateToolInput(id messageId: UUID, callId: String, data: Data) { guard let msgIdx = messages.firstIndex(where: { $0.id == messageId }), let toolIdx = messages[msgIdx].toolInvocations.firstIndex(where: { $0.id == callId }) else { return } - messages[msgIdx].toolInvocations[toolIdx].inputData = data + var updated = messages[msgIdx] + updated.toolInvocations[toolIdx].inputData = data + messages[msgIdx] = updated } private func updateToolOutput(id messageId: UUID, callId: String, data: Data) { guard let msgIdx = messages.firstIndex(where: { $0.id == messageId }), let toolIdx = messages[msgIdx].toolInvocations.firstIndex(where: { $0.id == callId }) else { return } - messages[msgIdx].toolInvocations[toolIdx].outputData = data - messages[msgIdx].toolInvocations[toolIdx].state = .complete + var updated = messages[msgIdx] + updated.toolInvocations[toolIdx].outputData = data + updated.toolInvocations[toolIdx].state = .complete + messages[msgIdx] = updated } } diff --git a/apps/swift/Sources/PackRat/Features/OfflineAI/FeatureFlag.swift b/apps/swift/Sources/PackRat/Features/OfflineAI/FeatureFlag.swift index 15865990a1..639b5a415a 100644 --- a/apps/swift/Sources/PackRat/Features/OfflineAI/FeatureFlag.swift +++ b/apps/swift/Sources/PackRat/Features/OfflineAI/FeatureFlag.swift @@ -16,7 +16,7 @@ extension Defaults.Keys { /// on-device LLM via MLX). When false, it uses `MockLocalLLMProvider` /// (canned responses). Defaults to `false` until the MLX integration is /// product-greenlit and the model bundle/download path is settled. - public static let useRealLocalLLM = Key("featureFlag.useRealLocalLLM", default: false) + public static let useRealLocalLLM = Key("featureFlag_useRealLocalLLM", default: false) } // MARK: - Provider factory @@ -26,8 +26,10 @@ extension Defaults.Keys { /// instantiating a concrete provider so flipping the flag at runtime swaps /// implementations on the next read. public enum LocalLLMProviderFactory { + private static let mlxProviderAvailable = false + public static func makeProvider() -> LocalLLMProvider { - if AppFeatureFlags.enableLocalAI && Defaults[.useRealLocalLLM] { + if AppFeatureFlags.enableLocalAI && mlxProviderAvailable && Defaults[.useRealLocalLLM] { return MLXLocalLLMProvider() } return MockLocalLLMProvider() diff --git a/apps/swift/Sources/PackRat/Features/OfflineAI/OfflineAIView.swift b/apps/swift/Sources/PackRat/Features/OfflineAI/OfflineAIView.swift index b6fc000535..ce46bf10f1 100644 --- a/apps/swift/Sources/PackRat/Features/OfflineAI/OfflineAIView.swift +++ b/apps/swift/Sources/PackRat/Features/OfflineAI/OfflineAIView.swift @@ -34,16 +34,15 @@ public struct OfflineAIView: View { private var providerSection: some View { Section("Provider") { Toggle("Use real on-device LLM (MLX)", isOn: $useRealLocalLLM) + .disabled(true) LabeledContent("Active provider") { - Text(useRealLocalLLM ? "MLXLocalLLMProvider (stub)" : "MockLocalLLMProvider") + Text("MockLocalLLMProvider") .font(.caption.monospaced()) .foregroundStyle(.secondary) } - if useRealLocalLLM { - Text("MLX provider is not yet wired. Submitting a prompt will surface a `notImplemented` error — this is expected until a follow-up PR adds the MLX dependency.") - .font(.caption) - .foregroundStyle(.secondary) - } + Text("The MLX provider is not available in this build. Offline AI uses the local mock provider until model packaging and runtime loading are production-ready.") + .font(.caption) + .foregroundStyle(.secondary) Text("Flag changes apply on next view appearance.") .font(.caption2) .foregroundStyle(.tertiary) diff --git a/apps/swift/Sources/PackRat/Features/PackTemplates/PackTemplateFormView.swift b/apps/swift/Sources/PackRat/Features/PackTemplates/PackTemplateFormView.swift index 54e3a82a10..55c6fed4a0 100644 --- a/apps/swift/Sources/PackRat/Features/PackTemplates/PackTemplateFormView.swift +++ b/apps/swift/Sources/PackRat/Features/PackTemplates/PackTemplateFormView.swift @@ -45,6 +45,7 @@ struct PackTemplateFormView: View { } } .pickerStyle(.menu) + .accessibilityIdentifier("template_category") } if let error { InlineErrorView(message: error).listRowBackground(Color.clear) diff --git a/apps/swift/Sources/PackRat/Features/PackTemplates/PackTemplatesView.swift b/apps/swift/Sources/PackRat/Features/PackTemplates/PackTemplatesView.swift index 1f72fd676e..f9d04d4772 100644 --- a/apps/swift/Sources/PackRat/Features/PackTemplates/PackTemplatesView.swift +++ b/apps/swift/Sources/PackRat/Features/PackTemplates/PackTemplatesView.swift @@ -8,11 +8,14 @@ struct PackTemplatesListView: View { @Bindable var viewModel: PackTemplatesViewModel @Binding var selectedId: String? var packsVM: PacksViewModel = PacksViewModel() + var showsGuestLimitInList = true @Environment(AuthManager.self) private var authManager @State private var showingNewTemplate = false #if os(iOS) @Environment(\.horizontalSizeClass) private var horizontalSizeClass - private var isCompact: Bool { horizontalSizeClass == .compact } + private var isCompact: Bool { + horizontalSizeClass == .compact && UIDevice.current.userInterfaceIdiom == .phone + } #else private var isCompact: Bool { false } #endif @@ -20,40 +23,35 @@ struct PackTemplatesListView: View { var body: some View { Group { if !authManager.isAuthenticated { - #if os(macOS) - Color.clear - #else - GuestLimitedView( - "Templates Require an Account", - subtitle: "Pack templates sync with your account so they can be reused across devices.", - systemImage: "doc.on.doc" - ) - #endif + if showsGuestLimitInList { + GuestLimitedView( + "Templates Require an Account", + subtitle: "Pack templates sync with your account so they can be reused across devices.", + systemImage: "doc.on.doc" + ) + } else { + Color.clear + } } else if viewModel.isLoading && viewModel.templates.isEmpty { ProgressView("Loading templates…").frame(maxWidth: .infinity, maxHeight: .infinity) } else if let error = viewModel.error, viewModel.templates.isEmpty { ErrorView(error, retry: { await viewModel.load() }) - } else if viewModel.templates.isEmpty { - EmptyStateView( - "No Templates Yet", - subtitle: "Templates let you quickly populate a pack with a standard gear list", - systemImage: "doc.on.doc" - ) } else { templateList } } .navigationTitle("Pack Templates") - .searchable(text: $viewModel.searchText, prompt: "Search templates") + .packTemplateSearchable(text: $viewModel.searchText) .task { if authManager.isAuthenticated && viewModel.templates.isEmpty { await viewModel.load() } } .refreshable { if authManager.isAuthenticated { await viewModel.load() } } .toolbar { - ToolbarItem(placement: .primaryAction) { - Button("New Template", systemImage: "plus") { - showingNewTemplate = true + if authManager.isAuthenticated { + ToolbarItem(placement: .primaryAction) { + Button("New Template", systemImage: "plus") { + showingNewTemplate = true + } + .accessibilityIdentifier("templates_new_template_button") } - .accessibilityIdentifier("templates_new_template_button") - .disabled(!authManager.isAuthenticated) } } .sheet(isPresented: $showingNewTemplate) { @@ -65,7 +63,23 @@ struct PackTemplatesListView: View { private var templateList: some View { List(selection: $selectedId) { - if !viewModel.officialTemplates.isEmpty { + if viewModel.filteredTemplates.isEmpty { + Section { + if viewModel.searchText.isEmpty { + EmptyStateView( + "No Templates Yet", + subtitle: "Templates let you quickly populate a pack with a standard gear list", + systemImage: "doc.on.doc", + actionLabel: "New Template", + action: { showingNewTemplate = true } + ) + } else { + ContentUnavailableView.search(text: viewModel.searchText) + } + } + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + } else if !viewModel.officialTemplates.isEmpty { Section("Official") { ForEach(viewModel.officialTemplates) { t in templateRow(t) @@ -105,6 +119,21 @@ struct PackTemplatesListView: View { } } +private extension View { + @ViewBuilder + func packTemplateSearchable(text: Binding) -> some View { + #if os(iOS) + self.searchable( + text: text, + placement: .navigationBarDrawer(displayMode: .always), + prompt: "Search templates" + ) + #else + self.searchable(text: text, prompt: "Search templates") + #endif + } +} + private struct TemplateRowView: View { let template: PackTemplate diff --git a/apps/swift/Sources/PackRat/Features/Packs/PackItemDetailView.swift b/apps/swift/Sources/PackRat/Features/Packs/PackItemDetailView.swift index 5998028c9f..81a2d67324 100644 --- a/apps/swift/Sources/PackRat/Features/Packs/PackItemDetailView.swift +++ b/apps/swift/Sources/PackRat/Features/Packs/PackItemDetailView.swift @@ -14,13 +14,12 @@ struct PackItemDetailView: View { NavigationStack { ScrollView { VStack(alignment: .leading, spacing: 20) { + summarySection metadataSection if let notes = item.notes, !notes.isEmpty { notesSection(notes) } - if isLoadingSimilar || !similarItems.isEmpty { - similarSection - } + similarSection } .padding(.bottom, 24) } @@ -49,6 +48,87 @@ struct PackItemDetailView: View { // MARK: - Metadata + private var summarySection: some View { + VStack(alignment: .leading, spacing: 16) { + HStack(alignment: .top, spacing: 14) { + itemImage + + VStack(alignment: .leading, spacing: 6) { + Text(item.name) + .font(.title2.bold()) + .fixedSize(horizontal: false, vertical: true) + if let description = item.description, !description.isEmpty { + Text(description) + .font(.body) + .foregroundStyle(.secondary) + } else { + Text("Pack item") + .font(.subheadline) + .foregroundStyle(.secondary) + } + } + + Spacer(minLength: 0) + } + + VStack(spacing: 0) { + detailRow("Weight", value: item.displayWeight.isEmpty ? "Not set" : item.displayWeight, symbol: "scalemass") + detailRow("Quantity", value: "\(item.quantity)", symbol: "number") + detailRow("Category", value: item.category?.capitalized ?? "Uncategorized", symbol: "tag") + detailRow("Pack Weight", value: packWeightLabel, symbol: "backpack") + if item.catalogItemId != nil { + detailRow("Catalog Match", value: "Linked", symbol: "link") + } + if item.isAIGenerated == true { + detailRow("Source", value: "AI generated", symbol: "sparkles") + } + } + .background(.background, in: RoundedRectangle(cornerRadius: 12)) + } + .padding(.horizontal) + } + + private var itemImage: some View { + ZStack { + RoundedRectangle(cornerRadius: 14) + .fill(.fill.secondary) + .frame(width: 72, height: 72) + + if let image = item.image, let url = URL(string: image) { + LazyImage(url: url) { state in + if let image = state.image { + image.resizable().scaledToFill() + } else { + Image(systemName: categorySymbol) + .font(.title2) + .foregroundStyle(.secondary) + } + } + .frame(width: 72, height: 72) + .clipShape(RoundedRectangle(cornerRadius: 14)) + } else { + Image(systemName: categorySymbol) + .font(.title2) + .foregroundStyle(.secondary) + } + } + } + + private func detailRow(_ title: String, value: String, symbol: String) -> some View { + LabeledContent { + Text(value) + .foregroundStyle(.primary) + } label: { + Label(title, systemImage: symbol) + .foregroundStyle(.secondary) + } + .padding(.horizontal, 14) + .padding(.vertical, 10) + .overlay(alignment: .bottom) { + Divider().padding(.leading, 42) + } + } + private var metadataSection: some View { VStack(alignment: .leading, spacing: 12) { // Weight + quantity row @@ -94,6 +174,28 @@ struct PackItemDetailView: View { .padding(.horizontal) } + private var categorySymbol: String { + switch item.category?.lowercased() { + case "shelter": return "tent" + case "sleep": return "moon.zzz" + case "food", "kitchen": return "fork.knife" + case "clothing": return "tshirt" + case "water": return "drop" + case "safety": return "cross.case" + case "pack": return "backpack" + default: return "archivebox" + } + } + + private var packWeightLabel: String { + switch (item.worn, item.consumable) { + case (true, true): return "Worn consumable" + case (true, false): return "Worn on body" + case (false, true): return "Consumable" + case (false, false): return "Base weight" + } + } + private func metaChip(value: String, label: String, symbol: String, color: Color) -> some View { VStack(alignment: .leading, spacing: 4) { HStack(spacing: 4) { @@ -150,6 +252,14 @@ struct PackItemDetailView: View { ProgressView() .frame(maxWidth: .infinity) .padding() + } else if similarItems.isEmpty { + ContentUnavailableView( + "No Similar Gear", + systemImage: "magnifyingglass", + description: Text("Catalog suggestions will appear when matching gear is available.") + ) + .frame(maxWidth: .infinity) + .padding(.vertical, 12) } else { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 12) { diff --git a/apps/swift/Sources/PackRat/Features/SeasonSuggestions/SeasonSuggestionsView.swift b/apps/swift/Sources/PackRat/Features/SeasonSuggestions/SeasonSuggestionsView.swift index 8608cd40b3..c779824fe3 100644 --- a/apps/swift/Sources/PackRat/Features/SeasonSuggestions/SeasonSuggestionsView.swift +++ b/apps/swift/Sources/PackRat/Features/SeasonSuggestions/SeasonSuggestionsView.swift @@ -9,9 +9,13 @@ final class SeasonSuggestionsService: Sendable { init(api: APIClient = .shared) { self.api = api } func getSuggestions(location: String, date: String) async throws -> SeasonSuggestionsResponse { + if VisualSampleData.isEnabled { + return VisualSampleData.seasonSuggestions(location: location) + } + let endpoint = Endpoint( .post, - "/api/season-suggestions", + "/api/season-suggestions/", body: ["location": location, "date": date] ) return try await api.send(endpoint) @@ -109,6 +113,7 @@ struct SeasonSuggestionsView: View { TextField("e.g. Yosemite, Pacific Crest Trail…", text: $viewModel.location) .submitLabel(.go) .onSubmit { Task { await viewModel.load() } } + .accessibilityIdentifier("season_suggestions_location") } header: { Text("Destination") } footer: { @@ -127,6 +132,7 @@ struct SeasonSuggestionsView: View { } label: { Label("Get Suggestions", systemImage: "sparkles") } + .accessibilityIdentifier("season_suggestions_submit") .disabled(viewModel.location.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.isLoading) } } @@ -164,6 +170,7 @@ struct SeasonSuggestionsView: View { } } } + .accessibilityIdentifier("season_suggestions_results") #if os(iOS) .listStyle(.insetGrouped) #endif diff --git a/apps/swift/Sources/PackRat/Features/TrailConditions/TrailConditionsView.swift b/apps/swift/Sources/PackRat/Features/TrailConditions/TrailConditionsView.swift index 3e5034799b..83b3b16e51 100644 --- a/apps/swift/Sources/PackRat/Features/TrailConditions/TrailConditionsView.swift +++ b/apps/swift/Sources/PackRat/Features/TrailConditions/TrailConditionsView.swift @@ -5,11 +5,14 @@ import SwiftUI struct TrailConditionsListView: View { @Bindable var viewModel: TrailConditionsViewModel @Binding var selectedId: String? + var showsGuestLimitInList = true @Environment(AuthManager.self) private var authManager @State private var showingSubmitSheet = false #if os(iOS) @Environment(\.horizontalSizeClass) private var horizontalSizeClass - private var isCompact: Bool { horizontalSizeClass == .compact } + private var isCompact: Bool { + horizontalSizeClass == .compact && UIDevice.current.userInterfaceIdiom == .phone + } #else private var isCompact: Bool { false } #endif @@ -17,15 +20,15 @@ struct TrailConditionsListView: View { var body: some View { Group { if !authManager.isAuthenticated { - #if os(macOS) - Color.clear - #else - GuestLimitedView( - "Trail Reports Require an Account", - subtitle: "Community trail conditions are shared through your PackRat account.", - systemImage: "figure.hiking" - ) - #endif + if showsGuestLimitInList { + GuestLimitedView( + "Trail Reports Require an Account", + subtitle: "Community trail conditions are shared through your PackRat account.", + systemImage: "figure.hiking" + ) + } else { + Color.clear + } } else if viewModel.isLoading && viewModel.reports.isEmpty { ProgressView("Loading reports…").frame(maxWidth: .infinity, maxHeight: .infinity) } else if let error = viewModel.error, viewModel.reports.isEmpty { @@ -45,10 +48,11 @@ struct TrailConditionsListView: View { .navigationTitle("Trail Conditions") .searchable(text: $viewModel.searchText, prompt: "Search trails") .toolbar { - ToolbarItem(placement: .primaryAction) { - Button("Submit Report", systemImage: "plus") { showingSubmitSheet = true } - .accessibilityIdentifier("trail_conditions_submit_report_button") - .disabled(!authManager.isAuthenticated) + if authManager.isAuthenticated { + ToolbarItem(placement: .primaryAction) { + Button("Submit Report", systemImage: "plus") { showingSubmitSheet = true } + .accessibilityIdentifier("trail_conditions_submit_report_button") + } } } .task { if authManager.isAuthenticated && viewModel.reports.isEmpty { await viewModel.load() } } diff --git a/apps/swift/Sources/PackRat/Features/Weather/WeatherView.swift b/apps/swift/Sources/PackRat/Features/Weather/WeatherView.swift index edb4c19264..de2c1368ae 100644 --- a/apps/swift/Sources/PackRat/Features/Weather/WeatherView.swift +++ b/apps/swift/Sources/PackRat/Features/Weather/WeatherView.swift @@ -1,6 +1,7 @@ import SwiftUI struct WeatherView: View { + @Environment(AuthManager.self) private var authManager @Bindable var viewModel: WeatherViewModel @State private var showingAlerts = false @State private var showingAlertPreferences = false @@ -11,45 +12,55 @@ struct WeatherView: View { } var body: some View { - List { - searchStateContent - .listRowSeparator(.hidden) - .listRowBackground(Color.clear) - - if !viewModel.savedLocations.isEmpty && viewModel.searchText.isEmpty && viewModel.searchResults.isEmpty { - savedLocationsSection - .listRowSeparator(.hidden) - .listRowBackground(Color.clear) - } - - if let forecast = viewModel.forecast { - forecastContent(forecast) - .listRowSeparator(.hidden) - .listRowBackground(Color.clear) - } else if viewModel.isLoadingForecast { - ProgressView("Loading forecast…") - .frame(maxWidth: .infinity) - .listRowSeparator(.hidden) - .listRowBackground(Color.clear) - } else if let error = viewModel.forecastError { - ErrorView(error, retry: { await viewModel.refresh() }) - .listRowSeparator(.hidden) - .listRowBackground(Color.clear) - } else if viewModel.savedLocations.isEmpty { - EmptyStateView( - "No Saved Locations", - subtitle: "Search for a city or ZIP code and save it to track the weather", + Group { + if !authManager.isAuthenticated { + GuestLimitedView( + "Weather Requires an Account", + subtitle: "Forecasts and alerts come from PackRat's weather service. Local packs and trips still work in guest mode.", systemImage: "cloud.sun" ) - .listRowSeparator(.hidden) - .listRowBackground(Color.clear) + } else { + List { + searchStateContent + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + + if !viewModel.savedLocations.isEmpty && viewModel.searchText.isEmpty && viewModel.searchResults.isEmpty { + savedLocationsSection + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } + + if let forecast = viewModel.forecast { + forecastContent(forecast) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } else if viewModel.isLoadingForecast { + ProgressView("Loading forecast…") + .frame(maxWidth: .infinity) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } else if let error = viewModel.forecastError { + ErrorView(error, retry: { await viewModel.refresh() }) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } else if viewModel.savedLocations.isEmpty { + EmptyStateView( + "No Saved Locations", + subtitle: "Search for a city or ZIP code and save it to track the weather", + systemImage: "cloud.sun" + ) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } + } + #if os(iOS) + .listStyle(.insetGrouped) + #else + .listStyle(.inset) + #endif } } - #if os(iOS) - .listStyle(.insetGrouped) - #else - .listStyle(.inset) - #endif .navigationTitle("Weather") #if os(iOS) .searchable( @@ -61,32 +72,42 @@ struct WeatherView: View { #else .searchable(text: $viewModel.searchText, isPresented: $isSearchPresented, prompt: "Search locations…") #endif - .onChange(of: viewModel.searchText) { viewModel.onSearchTextChanged() } - .refreshable { await viewModel.refresh() } + .onChange(of: viewModel.searchText) { + if authManager.isAuthenticated { + viewModel.onSearchTextChanged() + } + } + .refreshable { + if authManager.isAuthenticated { + await viewModel.refresh() + } + } .toolbar { - ToolbarItem(placement: alertsToolbarPlacement) { - Button { - showingAlerts = true - } label: { - Label("Alerts", systemImage: activeAlerts.isEmpty ? "bell" : "bell.badge.fill") - .foregroundStyle(activeAlerts.isEmpty ? Color.secondary : Color.red) + if authManager.isAuthenticated { + ToolbarItem(placement: alertsToolbarPlacement) { + Button { + showingAlerts = true + } label: { + Label("Alerts", systemImage: activeAlerts.isEmpty ? "bell" : "bell.badge.fill") + .foregroundStyle(activeAlerts.isEmpty ? Color.secondary : Color.red) + } + .disabled(viewModel.forecast == nil) + .accessibilityLabel("Alerts") + .accessibilityIdentifier("weather_alerts_button") } - .disabled(viewModel.forecast == nil) - .accessibilityLabel("Alerts") - .accessibilityIdentifier("weather_alerts_button") - } - if viewModel.isLoadingForecast && viewModel.forecast != nil { - ToolbarItem(placement: .secondaryAction) { - ProgressView().controlSize(.small) + if viewModel.isLoadingForecast && viewModel.forecast != nil { + ToolbarItem(placement: .secondaryAction) { + ProgressView().controlSize(.small) + } } - } - ToolbarItem(placement: preferencesToolbarPlacement) { - NavigationLink { - WeatherAlertPreferencesView() - } label: { - Label("Alert Preferences", systemImage: "slider.horizontal.3") + ToolbarItem(placement: preferencesToolbarPlacement) { + NavigationLink { + WeatherAlertPreferencesView() + } label: { + Label("Alert Preferences", systemImage: "slider.horizontal.3") + } + .accessibilityIdentifier("weather_alert_preferences_button") } - .accessibilityIdentifier("weather_alert_preferences_button") } } .sheet(isPresented: $showingAlerts) { diff --git a/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift b/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift index fcb803f81a..27fd1b84b9 100644 --- a/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift +++ b/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift @@ -195,9 +195,18 @@ struct AppNavigation: View { case .trips: TripsListView(viewModel: appState.tripsVM, selectedId: $state.selectedTripId) case .templates: - PackTemplatesListView(viewModel: appState.templatesVM, selectedId: $state.selectedTemplateId, packsVM: appState.packsVM) + PackTemplatesListView( + viewModel: appState.templatesVM, + selectedId: $state.selectedTemplateId, + packsVM: appState.packsVM, + showsGuestLimitInList: false + ) case .trailConditions: - TrailConditionsListView(viewModel: appState.trailConditionsVM, selectedId: $state.selectedReportId) + TrailConditionsListView( + viewModel: appState.trailConditionsVM, + selectedId: $state.selectedReportId, + showsGuestLimitInList: false + ) default: EmptyView() } diff --git a/apps/swift/Sources/PackRat/Network/AuthManager.swift b/apps/swift/Sources/PackRat/Network/AuthManager.swift index ff16d4bf0b..ccc985fbe3 100644 --- a/apps/swift/Sources/PackRat/Network/AuthManager.swift +++ b/apps/swift/Sources/PackRat/Network/AuthManager.swift @@ -331,9 +331,10 @@ final class AuthManager { updatedAt: nil ) - KeychainService.shared.saveSessionToken( - environment["PACKRAT_E2E_SESSION_TOKEN"] ?? "packrat-e2e-session" - ) + let sessionToken = environment["PACKRAT_E2E_SESSION_TOKEN"] + .flatMap { $0.isEmpty ? nil : $0 } + ?? "packrat-e2e-session" + KeychainService.shared.saveSessionToken(sessionToken) persistUser(user) isGuest = false currentUser = user diff --git a/apps/swift/Sources/PackRat/Services/CatalogService.swift b/apps/swift/Sources/PackRat/Services/CatalogService.swift index 2e8128e058..df126da50f 100644 --- a/apps/swift/Sources/PackRat/Services/CatalogService.swift +++ b/apps/swift/Sources/PackRat/Services/CatalogService.swift @@ -20,7 +20,10 @@ final class CatalogService: Sendable { } func semanticSearch(query: String, limit: Int = 10) async throws -> [CatalogItem] { - let endpoint = Endpoint(.get, "/api/catalog/search", query: ["q": query, "limit": "\(limit)"]) + let endpoint = Endpoint(.get, "/api/catalog/vector-search", query: ["q": query, "limit": "\(limit)"]) + if let wrapped = try? await api.send(endpoint, as: CatalogSearchResponse.self) { + return wrapped.items + } return try await api.send(endpoint) } } diff --git a/apps/swift/Sources/PackRat/Shared/FormSheetSizing.swift b/apps/swift/Sources/PackRat/Shared/FormSheetSizing.swift index ff52128d13..4e7f35ae81 100644 --- a/apps/swift/Sources/PackRat/Shared/FormSheetSizing.swift +++ b/apps/swift/Sources/PackRat/Shared/FormSheetSizing.swift @@ -4,11 +4,13 @@ extension View { @ViewBuilder func formSheetSize(minWidth: CGFloat = 520, idealWidth: CGFloat? = nil, minHeight: CGFloat = 520, idealHeight: CGFloat? = nil) -> some View { #if os(macOS) + let macWidth = max(minWidth, 540) + let macHeight = max(minHeight, 560) self.frame( - minWidth: minWidth, - idealWidth: idealWidth ?? minWidth, - minHeight: minHeight, - idealHeight: idealHeight ?? minHeight + minWidth: macWidth, + idealWidth: idealWidth ?? macWidth, + minHeight: macHeight, + idealHeight: idealHeight ?? macHeight ) #else self @@ -17,6 +19,12 @@ extension View { @ViewBuilder func packRatFormStyle() -> some View { + #if os(macOS) + self + .formStyle(.grouped) + .controlSize(.regular) + #else self.formStyle(.grouped) + #endif } } diff --git a/apps/swift/Sources/PackRat/Shared/VisualSampleData.swift b/apps/swift/Sources/PackRat/Shared/VisualSampleData.swift index e127a6ea5c..3dd553c630 100644 --- a/apps/swift/Sources/PackRat/Shared/VisualSampleData.swift +++ b/apps/swift/Sources/PackRat/Shared/VisualSampleData.swift @@ -68,6 +68,63 @@ enum VisualSampleData { Array(Set(guides.compactMap(\.category))).sorted() } + static func seasonSuggestions(location: String) -> SeasonSuggestionsResponse { + SeasonSuggestionsResponse( + suggestions: [ + SeasonSuggestion( + name: "Shoulder Season Overnight", + description: "Balanced kit for \(location) with warmth, rain protection, and reliable camp basics.", + category: "backpacking", + tags: ["shoulder season", "overnight"], + items: [ + SeasonSuggestionItem( + name: "Rain shell", + description: "Waterproof layer for shoulder-season weather.", + weight: 210, + weightUnit: "g", + quantity: 1, + category: "clothing", + consumable: false, + worn: false, + image: nil, + notes: "Keep this accessible for changing weather.", + catalogItemId: nil + ), + SeasonSuggestionItem( + name: "Headlamp", + description: "Reliable lighting for short autumn daylight.", + weight: 85, + weightUnit: "g", + quantity: 1, + category: "lighting", + consumable: false, + worn: false, + image: nil, + notes: "Pack fresh batteries before leaving.", + catalogItemId: nil + ), + SeasonSuggestionItem( + name: "Warm layer", + description: "Insulating layer for cool evenings and exposed breaks.", + weight: 320, + weightUnit: "g", + quantity: 1, + category: "clothing", + consumable: false, + worn: true, + image: nil, + notes: "Wear or keep near the top of the pack.", + catalogItemId: nil + ), + ] + ), + ], + totalInventoryItems: 3, + location: location, + season: "fall" + ) + } + static func catalogItems(matching query: String) -> [CatalogItem] { let allItems = [ CatalogItem( diff --git a/apps/swift/Sources/PackRatWatch/PackRatWatchApp.swift b/apps/swift/Sources/PackRatWatch/PackRatWatchApp.swift index 9b3781d1e2..c93a27f957 100644 --- a/apps/swift/Sources/PackRatWatch/PackRatWatchApp.swift +++ b/apps/swift/Sources/PackRatWatch/PackRatWatchApp.swift @@ -9,6 +9,9 @@ struct PackRatWatchApp: App { WatchRootView() .environment(connectivity) .task { + guard ProcessInfo.processInfo.environment["PACKRAT_WATCH_DISABLE_CONNECTIVITY"] != "1" else { + return + } connectivity.activate() } } @@ -20,6 +23,8 @@ private struct WatchRootView: View { var body: some View { switch ProcessInfo.processInfo.environment["PACKRAT_WATCH_SCREENSHOT_ROUTE"] { + case "dashboard": + TrailReadyView(snapshot: connectivity.snapshot, isPhoneReachable: connectivity.isPhoneReachable) case "checklist": WatchChecklistView(pack: connectivity.snapshot.pack) case "weather": @@ -74,6 +79,7 @@ private struct TrailReadyView: View { Text(tripSubtitle) .font(.footnote) .foregroundStyle(.secondary) + .lineLimit(2) .fixedSize(horizontal: false, vertical: true) Divider() @@ -84,11 +90,7 @@ private struct TrailReadyView: View { value: "\(snapshot.pack.packedItemCount)/\(snapshot.pack.totalItemCount)", symbol: "backpack" ) - WatchMetricRow( - title: "Weather", - value: "\(snapshot.weather.temperatureText) \(snapshot.weather.conditionText)", - symbol: snapshot.weather.symbolName - ) + WatchMetricRow(title: "Weather", value: snapshot.weather.temperatureText, symbol: snapshot.weather.symbolName) } } } else { @@ -115,7 +117,7 @@ private struct TrailReadyView: View { guard let trip = snapshot.trip else { return "Quick wrist access for the next pack, weather, and trail notes." } - return [trip.name, trip.locationName, trip.dateText] + return [trip.locationName, trip.dateText] .compactMap { $0 } .joined(separator: " - ") } diff --git a/apps/swift/Tests/PackRatMacUITests/MacHomeFeatureTests.swift b/apps/swift/Tests/PackRatMacUITests/MacHomeFeatureTests.swift deleted file mode 100644 index a0d7af9060..0000000000 --- a/apps/swift/Tests/PackRatMacUITests/MacHomeFeatureTests.swift +++ /dev/null @@ -1,56 +0,0 @@ -import XCTest - -final class MacHomeFeatureTests: MacUITestCase { - func testPrimaryHomeTileOpensPacksOnMac() { - goToSidebar("Home", expected: "Home") - tapHomeTile("home_tile_my_packs") - XCTAssertTrue( - app.buttons["New Pack"].waitForExistence(timeout: 8) - || app.staticTexts["Packs"].waitForExistence(timeout: 2), - "Packs content must appear after selecting the home tile" - ) - } - - func testShoppingListTileSupportsAddToggleClearAndDone() { - goToSidebar("Home", expected: "Home") - tapHomeTile("home_tile_shopping_list") - - XCTAssertTrue( - app.staticTexts["Shopping List Empty"].waitForExistence(timeout: 5) - || app.buttons["shopping_add_item"].waitForExistence(timeout: 5), - "Shopping List sheet must appear" - ) - - waitFor(app.buttons["shopping_add_item"], timeout: 5).tap() - XCTAssertTrue(app.textFields["shopping_item_name"].waitForExistence(timeout: 5)) - XCTAssertFalse(app.buttons["shopping_item_add"].isEnabled) - - let itemName = "Mac Stove \(Int(Date().timeIntervalSince1970))" - let nameField = waitFor(app.textFields["shopping_item_name"], timeout: 5) - nameField.tap() - nameField.typeText(itemName) - - waitFor(app.textFields["shopping_item_price"], timeout: 5).tap() - app.typeText("49.99") - - waitFor(app.buttons["shopping_item_add"], timeout: 5).tap() - XCTAssertTrue(app.staticTexts[itemName].waitForExistence(timeout: 5)) - - let toggle = app.buttons.matching(NSPredicate(format: "identifier BEGINSWITH 'shopping_toggle_'")).firstMatch - waitFor(toggle, timeout: 5).tap() - - app.buttons["shopping_done"].macTapIfExists() - } - - private func tapHomeTile(_ id: String) { - let tile = app.buttons[id] - if !tile.waitForExistence(timeout: 2) { - app.scrollViews.firstMatch.swipeUp() - } - if !tile.waitForExistence(timeout: 2) { - app.scrollViews.firstMatch.swipeUp() - } - waitFor(tile, timeout: 5, message: "\(id) must be visible on Home").tap() - } - -} diff --git a/apps/swift/Tests/PackRatMacUITests/MacNavigationTests.swift b/apps/swift/Tests/PackRatMacUITests/MacNavigationTests.swift deleted file mode 100644 index c8c47ca0ef..0000000000 --- a/apps/swift/Tests/PackRatMacUITests/MacNavigationTests.swift +++ /dev/null @@ -1,24 +0,0 @@ -import XCTest - -final class MacNavigationTests: MacUITestCase { - func testEverySidebarDestinationIsReachable() { - let destinations = [ - "Home", - "Packs", - "Trips", - "Weather", - "Assistant", - "Catalog", - "Templates", - "Trail Conditions", - "Feed", - "Guides", - "Gear Inventory", - "Wildlife" - ] - - for destination in destinations { - goToSidebar(destination) - } - } -} diff --git a/apps/swift/Tests/PackRatMacUITests/MacPackTripTests.swift b/apps/swift/Tests/PackRatMacUITests/MacPackTripTests.swift deleted file mode 100644 index c67737f1b7..0000000000 --- a/apps/swift/Tests/PackRatMacUITests/MacPackTripTests.swift +++ /dev/null @@ -1,77 +0,0 @@ -import XCTest - -final class MacPackTripTests: MacUITestCase { - func testCreateOpenAndAddItemToPack() { - let packName = uniqueName("Mac E2E Pack") - let itemName = "Mac Tent \(Int(Date().timeIntervalSince1970))" - - createPack(named: packName) - waitFor(app.staticTexts[packName], timeout: 15).tap() - - let addItem = app.buttons["Add Item"].firstMatch - waitFor(addItem, timeout: 10).tap() - - let itemNameField = waitFor(textInput("Name", alternateLabels: ["item_name"]), timeout: 10) - itemNameField.tap() - itemNameField.typeText(itemName) - - let weightField = app.textFields["0"].exists ? app.textFields["0"] : app.textFields["item_weight"] - if weightField.waitForExistence(timeout: 3) { - weightField.tap() - weightField.typeText("500") - } - - app.buttons["Add"].tap() - XCTAssertTrue(app.staticTexts[itemName].waitForExistence(timeout: 15)) - } - - func testCreateOpenAndDeleteTrip() { - let tripName = uniqueName("Mac E2E Trip") - createTrip(named: tripName) - - waitFor(app.staticTexts[tripName], timeout: 15).tap() - XCTAssertTrue(app.staticTexts[tripName].waitForExistence(timeout: 10)) - - goToSidebar("Trips") - let cell = app.cells.containing(.staticText, identifier: tripName).firstMatch - if cell.waitForExistence(timeout: 5) { - cell.swipeLeft() - let deleteButton = app.buttons["Delete"] - if deleteButton.waitForExistence(timeout: 3) { - deleteButton.tap() - waitForAbsence(app.staticTexts[tripName], timeout: 10) - } - } - } - - private func createPack(named name: String) { - goToSidebar("Packs") - waitFor(app.buttons["new_pack_button"].firstMatch, timeout: 10).tap() - - let nameField = waitFor(textInput("Pack Name", alternateLabels: ["pack_name"]), timeout: 10) - nameField.tap() - nameField.typeText(name) - - let categoryButton = app.buttons.matching( - NSPredicate(format: "label CONTAINS 'Category' OR label == 'None'") - ).firstMatch - if categoryButton.waitForExistence(timeout: 3) { - categoryButton.tap() - let hiking = app.buttons["Hiking"].firstMatch - if hiking.waitForExistence(timeout: 3) { hiking.tap() } - } - - app.buttons["Create"].tap() - waitFor(app.staticTexts[name], timeout: 15) - } - - private func createTrip(named name: String) { - goToSidebar("Trips") - waitFor(app.buttons["plan_trip_button"].firstMatch, timeout: 10).tap() - let nameField = waitFor(textInput("Trip Name", alternateLabels: ["trip_name"]), timeout: 10) - nameField.tap() - nameField.typeText(name) - app.buttons["Create"].tap() - waitFor(app.staticTexts[name], timeout: 15) - } -} diff --git a/apps/swift/Tests/PackRatMacUITests/MacSecondaryFeatureTests.swift b/apps/swift/Tests/PackRatMacUITests/MacSecondaryFeatureTests.swift deleted file mode 100644 index b758faffc0..0000000000 --- a/apps/swift/Tests/PackRatMacUITests/MacSecondaryFeatureTests.swift +++ /dev/null @@ -1,116 +0,0 @@ -import XCTest - -final class MacSecondaryFeatureTests: MacUITestCase { - func testAssistantInputSendAndClearControlsOnMac() { - goToSidebar("Assistant", expected: "AI Assistant") - - XCTAssertTrue(app.staticTexts["PackRat AI"].waitForExistence(timeout: 8)) - let input = waitFor(textInput( - "Ask about gear, trips, packing...", - alternateLabels: ["Ask about gear, trips, packing…", "chat_input"] - ), timeout: 5) - let send = waitFor(firstExisting([ - app.buttons["chat_send"], - app.buttons["Arrow Up Circle"], - app.buttons.matching(NSPredicate(format: "label CONTAINS[c] 'Arrow Up Circle'")).firstMatch - ], timeout: 3), timeout: 5) - XCTAssertFalse(send.isEnabled) - - input.tap() - input.typeText("Hi") - XCTAssertTrue(send.isEnabled) - send.tap() - XCTAssertTrue(app.staticTexts["Hi"].waitForExistence(timeout: 8)) - - let clear = app.buttons["Clear"].firstMatch - if clear.exists { - clear.tap() - } - } - - func testCatalogSearchAndClearControlsOnMac() { - goToSidebar("Catalog", expected: "Search the Gear Catalog") - - let searchField = textInput( - "Search tents, packs, sleeping bags…", - alternateLabels: ["Search tents, packs, sleeping bags...", "catalog_search"] - ) - waitFor(searchField, timeout: 8).tap() - searchField.typeText("tent") - - let loading = app.activityIndicators.firstMatch - _ = loading.waitForExistence(timeout: 2) - waitForSearchToSettle(loading) - - XCTAssertTrue( - app.staticTexts.matching( - NSPredicate(format: "label CONTAINS[c] 'tent' OR label CONTAINS[c] 'oz' OR label CONTAINS[c] 'lb' OR label CONTAINS[c] 'no results'") - ).firstMatch.waitForExistence(timeout: 10) - || app.buttons["catalog_search_clear"].waitForExistence(timeout: 2), - "Catalog must show search results or a no-results state" - ) - - let clearButton = app.buttons.matching( - NSPredicate(format: "label CONTAINS[c] 'clear' OR label CONTAINS[c] 'xmark'") - ).firstMatch - if clearButton.waitForExistence(timeout: 3) { - clearButton.tap() - XCTAssertTrue(app.staticTexts["Search the Gear Catalog"].waitForExistence(timeout: 5)) - } - } - - func testTemplateFormControlsOnMac() { - goToSidebar("Templates", expected: "New Template") - waitFor(app.buttons["new_template_button"].firstMatch, timeout: 10).tap() - - XCTAssertTrue(textInput("Name", alternateLabels: ["template_name"]).waitForExistence(timeout: 5)) - XCTAssertTrue( - app.buttons.matching(NSPredicate(format: "label CONTAINS 'Category'")).firstMatch.waitForExistence(timeout: 5) - || app.staticTexts["Category"].waitForExistence(timeout: 2), - "Template form must expose category controls" - ) - app.buttons["Cancel"].macTapIfExists() - } - - func testFeedComposerControlsOnMac() { - goToSidebar("Feed", expected: "Community Feed") - waitFor(app.buttons["new_post_button"].firstMatch, timeout: 10).tap() - - let editor = waitFor(app.textViews["feed_compose_caption"], timeout: 8) - let post = waitFor(app.buttons["Post"], timeout: 5) - XCTAssertFalse(post.isEnabled) - XCTAssertTrue( - app.staticTexts["feed_compose_counter"].waitForExistence(timeout: 5) - || app.staticTexts.matching(NSPredicate(format: "label CONTAINS '/ 500'")).firstMatch.waitForExistence(timeout: 2), - "Feed composer must show the character counter" - ) - - editor.tap() - editor.typeText("Mac E2E composer check") - XCTAssertTrue(post.isEnabled) - app.buttons["Cancel"].macTapIfExists() - } - - func testTrailReportFormControlsOnMac() { - goToSidebar("Trail Conditions", expected: "Submit Report") - waitFor(app.buttons["trail_submit_report_toolbar"].firstMatch, timeout: 10).tap() - - XCTAssertTrue(textInput("Trail Name", alternateLabels: ["trail_name"]).waitForExistence(timeout: 5)) - XCTAssertFalse(app.buttons["Submit"].isEnabled) - - for hazard in ["Downed trees", "Muddy sections", "Ice"] { - XCTAssertTrue( - toggleControl(hazard, alternateLabels: ["trail_hazard_\(hazard.replacingOccurrences(of: " ", with: "_"))"]) - .waitForExistence(timeout: 5) - ) - } - - app.buttons["Cancel"].macTapIfExists() - } - - private func waitForSearchToSettle(_ indicator: XCUIElement) { - let predicate = NSPredicate(format: "exists == false") - let expectation = XCTNSPredicateExpectation(predicate: predicate, object: indicator) - _ = XCTWaiter.wait(for: [expectation], timeout: 15) - } -} diff --git a/apps/swift/Tests/PackRatMacUITests/MacSmokeTests.swift b/apps/swift/Tests/PackRatMacUITests/MacSmokeTests.swift deleted file mode 100644 index 26d6b21db4..0000000000 --- a/apps/swift/Tests/PackRatMacUITests/MacSmokeTests.swift +++ /dev/null @@ -1,52 +0,0 @@ -import XCTest - -final class MacSmokeTests: XCTestCase { - func testLoginScreenAppearsOnMac() { - let app = XCUIApplication() - app.launchArguments.append("--disable-animations") - app.launchArguments.append("--reset-auth") - app.launch() - defer { app.terminate() } - - XCTAssertTrue( - app.textFields["login_email"].waitForExistence(timeout: 10), - "macOS app should launch to the login screen when auth is reset" - ) - XCTAssertTrue(app.secureTextFields["login_password"].exists) - XCTAssertTrue(app.buttons["login_submit"].exists) - } - - func testSuccessfulLoginOnMacReachesPrimaryChrome() throws { - let app = XCUIApplication() - app.launchArguments.append("--disable-animations") - app.launchArguments.append("--reset-auth") - if let apiBaseURL = ProcessInfo.processInfo.environment["E2E_API_BASE_URL"], !apiBaseURL.isEmpty { - app.launchEnvironment["E2E_API_BASE_URL"] = apiBaseURL - } - app.launch() - defer { app.terminate() } - - let email = ProcessInfo.processInfo.environment["E2E_EMAIL"] ?? "" - let password = ProcessInfo.processInfo.environment["E2E_PASSWORD"] ?? "" - guard !email.isEmpty, !password.isEmpty else { - throw XCTSkip("E2E_EMAIL and E2E_PASSWORD are required for macOS UI smoke tests") - } - - let emailField = app.textFields["login_email"] - XCTAssertTrue(emailField.waitForExistence(timeout: 10)) - emailField.tap() - emailField.typeText(email) - - let passwordField = app.secureTextFields["login_password"] - passwordField.tap() - passwordField.typeText(password) - - app.buttons["login_submit"].tap() - - let homeTitle = app.staticTexts["Home"] - XCTAssertTrue( - homeTitle.waitForExistence(timeout: 20), - "macOS app should reach the authenticated primary interface after login" - ) - } -} diff --git a/apps/swift/Tests/PackRatMacUITests/MacUITestCase.swift b/apps/swift/Tests/PackRatMacUITests/MacUITestCase.swift deleted file mode 100644 index bb8b524496..0000000000 --- a/apps/swift/Tests/PackRatMacUITests/MacUITestCase.swift +++ /dev/null @@ -1,164 +0,0 @@ -import XCTest - -class MacUITestCase: XCTestCase { - var app: XCUIApplication! - - override func setUpWithError() throws { - continueAfterFailure = false - app = XCUIApplication() - app.launchArguments.append("--disable-animations") - app.launchArguments.append("--ui-testing") - app.launchArguments.append("--reset-auth") - if let apiBaseURL = ProcessInfo.processInfo.environment["E2E_API_BASE_URL"], !apiBaseURL.isEmpty { - app.launchEnvironment["E2E_API_BASE_URL"] = apiBaseURL - } - app.launch() - try loginIfNeeded() - } - - override func tearDownWithError() throws { - app.terminate() - try super.tearDownWithError() - } - - func loginIfNeeded() throws { - if app.staticTexts["Home"].waitForExistence(timeout: 2) { return } - - let email = ProcessInfo.processInfo.environment["E2E_EMAIL"] ?? "" - let password = ProcessInfo.processInfo.environment["E2E_PASSWORD"] ?? "" - guard !email.isEmpty, !password.isEmpty else { - throw XCTSkip("E2E_EMAIL and E2E_PASSWORD are required for macOS UI tests") - } - - let emailField = app.textFields["login_email"] - XCTAssertTrue(emailField.waitForExistence(timeout: 10), "Login screen must appear") - emailField.tap() - emailField.typeText(email) - - let passwordField = app.secureTextFields["login_password"] - passwordField.tap() - passwordField.typeText(password) - - app.buttons["login_submit"].tap() - XCTAssertTrue( - app.staticTexts["Home"].waitForExistence(timeout: 20), - "Home content must appear after login" - ) - } - - func goToSidebar(_ label: String, expected: String? = nil) { - let destinations = [ - "Home": "home", - "Packs": "packs", - "Trips": "trips", - "Weather": "weather", - "Assistant": "chat", - "Catalog": "catalog", - "Templates": "templates", - "Trail Conditions": "trailConditions", - "Feed": "feed", - "Guides": "guides", - "Gear Inventory": "gearInventory", - "Wildlife": "wildlife" - ] - guard let rawValue = destinations[label] else { - XCTFail("Unknown sidebar item '\(label)'") - return - } - - waitFor(app.buttons["sidebar_nav_\(rawValue)"], timeout: 5, message: "\(label) sidebar item must exist").tap() - waitFor(app.descendants(matching: .any)["screen_\(rawValue)"], timeout: 8, message: "\(label) screen must appear after sidebar selection") - - if let expectedLabel = expected { - XCTAssertTrue( - firstExisting([ - app.staticTexts[expectedLabel], - app.buttons[expectedLabel], - app.searchFields[expectedLabel], - app.textFields[expectedLabel] - ], timeout: 8).exists, - "\(expectedLabel) content must appear after selecting \(label)" - ) - } - } - - func firstExisting(_ elements: [XCUIElement], timeout: TimeInterval = 5) -> XCUIElement { - let deadline = Date().addingTimeInterval(timeout) - repeat { - if let element = elements.first(where: { $0.exists }) { - return element - } - RunLoop.current.run(until: Date().addingTimeInterval(0.1)) - } while Date() < deadline - - return elements.first ?? app.staticTexts.firstMatch - } - - func textInput(_ label: String, alternateLabels: [String] = []) -> XCUIElement { - let labels = [label] + alternateLabels - let candidates = labels.flatMap { candidate in - [ - app.textFields[candidate], - app.searchFields[candidate], - app.secureTextFields[candidate], - app.textViews[candidate], - app.descendants(matching: .any)[candidate] - ] - } - return firstExisting(candidates, timeout: 3) - } - - func toggleControl(_ label: String, alternateLabels: [String] = []) -> XCUIElement { - let labels = [label] + alternateLabels - let candidates = labels.flatMap { candidate in - [ - app.switches[candidate], - app.checkBoxes[candidate], - app.buttons[candidate], - app.descendants(matching: .any)[candidate] - ] - } - return firstExisting(candidates, timeout: 3) - } - - @discardableResult - func waitFor(_ element: XCUIElement, timeout: TimeInterval = 10, message: String? = nil) -> XCUIElement { - let msg = message ?? "\(element.description) did not appear within \(timeout)s" - XCTAssertTrue(element.waitForExistence(timeout: timeout), msg) - return element - } - - func waitForAbsence(_ element: XCUIElement, timeout: TimeInterval = 10) { - let predicate = NSPredicate(format: "exists == false") - let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element) - let result = XCTWaiter.wait(for: [expectation], timeout: timeout) - XCTAssertEqual(result, .completed, "\(element.description) should have disappeared") - } - - func uniqueName(_ prefix: String) -> String { - "\(prefix) \(Int(Date().timeIntervalSince1970))" - } - - override func tearDown() { - if let testRun, testRun.totalFailureCount > 0 { - let screenshot = XCUIScreen.main.screenshot() - let attachment = XCTAttachment(screenshot: screenshot) - attachment.name = "Failure-\(name)" - attachment.lifetime = .keepAlways - add(attachment) - - let dump = app?.debugDescription ?? "no app" - let textAttachment = XCTAttachment(string: dump) - textAttachment.name = "Hierarchy-\(name)" - textAttachment.lifetime = .keepAlways - add(textAttachment) - } - super.tearDown() - } -} - -extension XCUIElement { - func macTapIfExists() { - if exists { tap() } - } -} diff --git a/apps/swift/Tests/PackRatMacUITests/MacWeatherTests.swift b/apps/swift/Tests/PackRatMacUITests/MacWeatherTests.swift deleted file mode 100644 index 7953e24606..0000000000 --- a/apps/swift/Tests/PackRatMacUITests/MacWeatherTests.swift +++ /dev/null @@ -1,49 +0,0 @@ -import XCTest - -final class MacWeatherTests: MacUITestCase { - func testLocationSearchAndForecastLoadOnMac() { - goToSidebar("Weather") - - let searchField = textInput( - "Search locations...", - alternateLabels: ["Search locations…", "weather_location_search"] - ) - waitFor(searchField, timeout: 10).tap() - searchField.typeText("Denver") - - let result = app.buttons.matching(NSPredicate(format: "label CONTAINS 'Denver'")).firstMatch - waitFor(result, timeout: 10).tap() - - XCTAssertTrue( - app.staticTexts["10-Day Forecast"].waitForExistence(timeout: 20) - || app.staticTexts.matching(NSPredicate(format: "label CONTAINS 'Forecast'")).firstMatch.waitForExistence(timeout: 20), - "Forecast content must load after selecting a Denver location" - ) - } - - func testAlertPreferencesToolbarFlowOnMac() { - goToSidebar("Weather") - - let preferences = app.buttons["weather_alert_preferences_button"].firstMatch - if preferences.waitForExistence(timeout: 3) { - preferences.tap() - } else { - openOverflowMenu() - waitFor(app.buttons["Alert Preferences"], timeout: 8).tap() - } - - XCTAssertTrue(toggleControl("Weather Notifications", alternateLabels: ["weather_notifications_toggle"]).waitForExistence(timeout: 5)) - let highWinds = waitFor(toggleControl("High Winds", alternateLabels: ["high_winds_toggle"]), timeout: 5) - highWinds.tap() - highWinds.tap() - } - - private func openOverflowMenu() { - let overflow = app.buttons["OverflowBarButtonItem"] - if overflow.waitForExistence(timeout: 2) { - overflow.tap() - return - } - waitFor(app.buttons.matching(NSPredicate(format: "identifier == 'OverflowBarButtonItem'")).firstMatch).tap() - } -} diff --git a/apps/swift/Tests/PackRatTests/OfflineAITests.swift b/apps/swift/Tests/PackRatTests/OfflineAITests.swift index b6ceee9bdb..33e3c7e06d 100644 --- a/apps/swift/Tests/PackRatTests/OfflineAITests.swift +++ b/apps/swift/Tests/PackRatTests/OfflineAITests.swift @@ -1,4 +1,5 @@ import Foundation +import Defaults import Testing @testable import PackRat @@ -258,3 +259,19 @@ struct OfflineAIViewModelTests { #expect(vm.state == .idle) } } + +// MARK: - LocalLLMProviderFactory + +@Suite("LocalLLMProviderFactory") +@MainActor +struct LocalLLMProviderFactoryTests { + @Test("uses mock provider even when stale MLX debug preference is enabled") + func staleMLXPreferenceUsesMockProvider() { + Defaults[.useRealLocalLLM] = true + defer { Defaults[.useRealLocalLLM] = false } + + let provider = LocalLLMProviderFactory.makeProvider() + + #expect(provider is MockLocalLLMProvider) + } +} diff --git a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift index 626f17c718..ab67c9e3ef 100644 --- a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift +++ b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift @@ -36,19 +36,26 @@ class AppUITestCase: XCTestCase { // bearer token accepted by the worker, avoiding brittle UI sign-in // while still exercising authenticated API routes. app.launchArguments.append("--reset-auth") + // Feature suites are not login tests. Let the known E2E credentials + // seed app auth after the form submit, while AuthTests keeps covering + // the visible auth flows with its own setup. + app.launchArguments.append("--allow-e2e-login-seed") app.launchArguments.append(contentsOf: additionalLaunchArguments) if let apiBaseURL = ProcessInfo.processInfo.environment["E2E_API_BASE_URL"], !apiBaseURL.isEmpty { app.launchEnvironment["E2E_API_BASE_URL"] = apiBaseURL } + injectE2EAuthEnvironment() let bundle = Bundle(for: AppUITestCase.self) - let seededAuthToken = - (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_SESSION_TOKEN") as? String) - ?? ProcessInfo.processInfo.environment["PACKRAT_E2E_SESSION_TOKEN"] + let seededAuthEmail = + ProcessInfo.processInfo.environment["PACKRAT_E2E_EMAIL"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_EMAIL") as? String) ?? "" - if !seededAuthToken.isEmpty { + if !seededAuthEmail.isEmpty { let runnerEnvironment = ProcessInfo.processInfo.environment app.launchArguments.append("--seed-e2e-auth") - app.launchEnvironment["PACKRAT_E2E_SESSION_TOKEN"] = seededAuthToken + app.launchEnvironment["PACKRAT_E2E_SESSION_TOKEN"] = runnerEnvironment["PACKRAT_E2E_SESSION_TOKEN"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_SESSION_TOKEN") as? String) + ?? "" app.launchEnvironment["PACKRAT_E2E_EMAIL"] = runnerEnvironment["PACKRAT_E2E_EMAIL"] ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_EMAIL") as? String) ?? "" @@ -122,6 +129,23 @@ class AppUITestCase: XCTestCase { ) } + private func injectE2EAuthEnvironment() { + let bundle = Bundle(for: AppUITestCase.self) + let runnerEnvironment = ProcessInfo.processInfo.environment + app.launchEnvironment["PACKRAT_E2E_EMAIL"] = runnerEnvironment["PACKRAT_E2E_EMAIL"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_EMAIL") as? String) + ?? "" + app.launchEnvironment["PACKRAT_E2E_PASSWORD"] = runnerEnvironment["PACKRAT_E2E_PASSWORD"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_PASSWORD") as? String) + ?? "" + app.launchEnvironment["PACKRAT_E2E_SESSION_TOKEN"] = runnerEnvironment["PACKRAT_E2E_SESSION_TOKEN"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_SESSION_TOKEN") as? String) + ?? "" + app.launchEnvironment["PACKRAT_E2E_USER_ID"] = runnerEnvironment["PACKRAT_E2E_USER_ID"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_USER_ID") as? String) + ?? "" + } + func submitLoginForm() { #if os(macOS) // macOS can show a password/autofill popover over the submit button @@ -141,8 +165,8 @@ class AppUITestCase: XCTestCase { #if os(iOS) return app.tabBars.firstMatch.waitForExistence(timeout: timeout) #elseif os(macOS) - return app.otherElements["app_navigation"].waitForExistence(timeout: timeout) - || app.outlines["app_sidebar"].waitForExistence(timeout: 1) + return app.outlines["app_sidebar"].waitForExistence(timeout: timeout) + || app.otherElements["app_navigation"].waitForExistence(timeout: 1) || app.staticTexts["Home"].waitForExistence(timeout: 1) || app.outlines.firstMatch.waitForExistence(timeout: 1) #else @@ -232,8 +256,8 @@ class AppUITestCase: XCTestCase { } let direct = app.tabBars.buttons[label] - if direct.exists { - direct.tap() + if direct.waitForExistence(timeout: 5) { + tapTabBarButton(direct) return } @@ -272,11 +296,7 @@ class AppUITestCase: XCTestCase { } } - XCTAssertTrue( - direct.waitForExistence(timeout: 5), - "Primary tab '\(label)' not found in tab bar" - ) - direct.tap() + XCTFail("Primary tab '\(label)' not found in tab bar") } func goToHomeAction(_ title: String) { @@ -308,6 +328,19 @@ class AppUITestCase: XCTestCase { let tabBarTop = app.tabBars.firstMatch.exists ? app.tabBars.firstMatch.frame.minY : app.frame.maxY return element.frame.minY >= 0 && element.frame.maxY <= tabBarTop - 12 } + + private func tapTabBarButton(_ element: XCUIElement) { + let frame = element.frame + guard frame.width > 0, frame.height > 0 else { + element.tap() + return + } + let coordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0)) + .withOffset(CGVector(dx: frame.midX, dy: frame.midY)) + coordinate.tap() + Thread.sleep(forTimeInterval: 0.35) + coordinate.tap() + } #endif // MARK: - Wait helpers @@ -326,6 +359,30 @@ class AppUITestCase: XCTestCase { XCTAssertEqual(result, .completed, "\(element.description) should have disappeared") } + func waitForStaticText(containing text: String, timeout: TimeInterval = 10) -> Bool { + let predicate = NSPredicate { [weak self] _, _ in + guard let self else { return false } + return self.app.staticTexts.allElementsBoundByIndex.contains { element in + element.exists && element.label.localizedCaseInsensitiveContains(text) + } + } + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: nil) + return XCTWaiter.wait(for: [expectation], timeout: timeout) == .completed + } + + func waitForElement(identifier: String, containing text: String, timeout: TimeInterval = 10) -> Bool { + let predicate = NSPredicate { [weak self] _, _ in + guard let self else { return false } + return self.app.descendants(matching: .any).matching(identifier: identifier) + .allElementsBoundByIndex + .contains { element in + element.exists && element.label.localizedCaseInsensitiveContains(text) + } + } + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: nil) + return XCTWaiter.wait(for: [expectation], timeout: timeout) == .completed + } + /// Attaches a screenshot + accessibility tree dump on failure for triage. override func tearDown() { if let testRun, testRun.totalFailureCount > 0 { diff --git a/apps/swift/Tests/PackRatUITests/AuthTests.swift b/apps/swift/Tests/PackRatUITests/AuthTests.swift index 2fe69979dc..d66b170190 100644 --- a/apps/swift/Tests/PackRatUITests/AuthTests.swift +++ b/apps/swift/Tests/PackRatUITests/AuthTests.swift @@ -73,6 +73,20 @@ final class AuthTests: AppUITestCase { XCTAssertTrue(app.buttons["Sign In or Create Account"].exists) XCTAssertFalse(app.buttons["Try Again"].exists) XCTAssertFalse(app.staticTexts["Connection Needed"].exists) + + app.buttons["Done"].tapIfExists() + goToHomeAction("Catalog") + XCTAssertTrue(app.staticTexts["Catalog Requires an Account"].waitForExistence(timeout: 10)) + XCTAssertTrue(app.buttons["Sign In or Create Account"].exists) + XCTAssertFalse(app.buttons["Try Again"].exists) + XCTAssertFalse(app.staticTexts["Connection Needed"].exists) + + app.buttons["Done"].tapIfExists() + goToHomeAction("Weather") + XCTAssertTrue(app.staticTexts["Weather Requires an Account"].waitForExistence(timeout: 10)) + XCTAssertTrue(app.buttons["Sign In or Create Account"].exists) + XCTAssertFalse(app.buttons["Try Again"].exists) + XCTAssertFalse(app.staticTexts["Connection Needed"].exists) } func testGuestSeesNativeSignInStateForAITools() { diff --git a/apps/swift/Tests/PackRatUITests/ChatMacOSTests.swift b/apps/swift/Tests/PackRatUITests/ChatMacOSTests.swift index a769763391..9ef5f6cafe 100644 --- a/apps/swift/Tests/PackRatUITests/ChatMacOSTests.swift +++ b/apps/swift/Tests/PackRatUITests/ChatMacOSTests.swift @@ -55,7 +55,7 @@ final class ChatMacOSTests: AppUITestCase { // User bubble should show "Hi" XCTAssertTrue( - app.staticTexts["Hi"].waitForExistence(timeout: 5), + waitForElement(identifier: "chat_message_user", containing: "Hi", timeout: 5), "User message bubble must appear after sending" ) @@ -65,6 +65,11 @@ final class ChatMacOSTests: AppUITestCase { } let exp = XCTNSPredicateExpectation(predicate: inputCleared, object: nil) _ = XCTWaiter.wait(for: [exp], timeout: 8) + + XCTAssertTrue( + waitForElement(identifier: "chat_message_assistant", containing: "three essential items", timeout: 15), + "Assistant response must stream back deterministic E2E content" + ) } func testClearChatHistoryButton() { diff --git a/apps/swift/Tests/PackRatUITests/ChatTests.swift b/apps/swift/Tests/PackRatUITests/ChatTests.swift index 725e026806..e2644e2626 100644 --- a/apps/swift/Tests/PackRatUITests/ChatTests.swift +++ b/apps/swift/Tests/PackRatUITests/ChatTests.swift @@ -57,7 +57,7 @@ final class ChatTests: AppUITestCase { // User message bubble should show "Hi" XCTAssertTrue( - app.staticTexts["Hi"].waitForExistence(timeout: 5), + waitForElement(identifier: "chat_message_user", containing: "Hi", timeout: 5), "User message bubble must appear after sending" ) @@ -68,6 +68,11 @@ final class ChatTests: AppUITestCase { } let exp = XCTNSPredicateExpectation(predicate: inputCleared, object: nil) _ = XCTWaiter.wait(for: [exp], timeout: 8) + + XCTAssertTrue( + waitForElement(identifier: "chat_message_assistant", containing: "three essential items", timeout: 15), + "Assistant response must stream back deterministic E2E content" + ) } func testClearChatHistoryButton() { diff --git a/apps/swift/Tests/PackRatUITests/HomeTileTests.swift b/apps/swift/Tests/PackRatUITests/HomeTileTests.swift index 61f616342e..fc275257cf 100644 --- a/apps/swift/Tests/PackRatUITests/HomeTileTests.swift +++ b/apps/swift/Tests/PackRatUITests/HomeTileTests.swift @@ -6,36 +6,53 @@ final class HomeTileTests: AppUITestCase { let destinationTitle: String? } - private let navigationTiles: [Tile] = [ - Tile(id: "home_tile_my_packs", destinationTitle: "Packs"), - Tile(id: "home_tile_trips", destinationTitle: "Trips"), - Tile(id: "home_tile_weather", destinationTitle: "Weather"), - Tile(id: "home_tile_ai_assistant", destinationTitle: "AI Assistant"), - Tile(id: "home_tile_gear_inventory", destinationTitle: "Gear Inventory"), - Tile(id: "home_tile_pack_templates", destinationTitle: "Pack Templates"), - Tile(id: "home_tile_guides", destinationTitle: "Guides"), - Tile(id: "home_tile_catalog", destinationTitle: "Gear Catalog"), - Tile(id: "home_tile_community_feed", destinationTitle: "Community Feed"), - Tile(id: "home_tile_trail_conditions", destinationTitle: "Trail Conditions"), - Tile(id: "home_tile_wildlife_id", destinationTitle: "Wildlife ID") + private let primaryNavigationTiles: [Tile] = [ + Tile(id: "home_action_mypacks", destinationTitle: "Packs"), + Tile(id: "home_action_trips", destinationTitle: "Trips"), + Tile(id: "home_action_weather", destinationTitle: "Weather"), + Tile(id: "home_action_aiassistant", destinationTitle: "AI Assistant"), ] - func testEveryHomeNavigationTileOpensDestination() { - for tile in navigationTiles { - goHome() - tapHomeTile(tile.id) + private let planningNavigationTiles: [Tile] = [ + Tile(id: "home_action_aipacks", destinationTitle: "AI Packs"), + Tile(id: "home_action_gearinventory", destinationTitle: "Gear Inventory"), + Tile(id: "home_action_packtemplates", destinationTitle: "Pack Templates"), + ] - guard let destinationTitle = tile.destinationTitle else { continue } - XCTAssertTrue( - destinationExists(destinationTitle, timeout: 8), - "\(tile.id) must open \(destinationTitle)" - ) + private var exploreNavigationTiles: [Tile] { + var tiles: [Tile] = [ + Tile(id: "home_action_guides", destinationTitle: "Guides"), + Tile(id: "home_action_catalog", destinationTitle: "Gear Catalog"), + ] + + if UITestFeatureFlags.enableFeed { + tiles.append(Tile(id: "home_action_communityfeed", destinationTitle: "Feed")) + } + if UITestFeatureFlags.enableTrailConditions { + tiles.append(Tile(id: "home_action_trailconditions", destinationTitle: "Trail Conditions")) } + if UITestFeatureFlags.enableWildlifeIdentification { + tiles.append(Tile(id: "home_action_wildlifeid", destinationTitle: "Wildlife")) + } + + return tiles + } + + func testPrimaryHomeNavigationTilesOpenDestinations() { + assertHomeTilesOpenDestinations(primaryNavigationTiles) + } + + func testPlanningHomeNavigationTilesOpenDestinations() { + assertHomeTilesOpenDestinations(planningNavigationTiles) + } + + func testExploreHomeNavigationTilesOpenDestinations() { + assertHomeTilesOpenDestinations(exploreNavigationTiles) } func testSeasonSuggestionsTileOpensAndDismissesSheet() { goHome() - tapHomeTile("home_tile_season_suggestions") + tapHomeTile("home_action_seasonsuggestions") XCTAssertTrue( app.staticTexts["AI-Powered Packing Tips"].waitForExistence(timeout: 5) @@ -45,9 +62,13 @@ final class HomeTileTests: AppUITestCase { app.buttons["Done"].tapIfExists() } - func testShoppingListTileSupportsAddToggleClearAndDone() { + func testShoppingListTileSupportsAddToggleClearAndDone() throws { + guard UITestFeatureFlags.enableShoppingList else { + throw XCTSkip("Shopping List is disabled by feature flags") + } + goHome() - tapHomeTile("home_tile_shopping_list") + tapHomeTile("home_action_shoppinglist") XCTAssertTrue( app.navigationBars.matching(NSPredicate(format: "identifier BEGINSWITH 'Shopping List'")).firstMatch @@ -125,6 +146,19 @@ final class HomeTileTests: AppUITestCase { waitFor(tile, timeout: 5, message: "\(id) must be visible on Home").tap() } + private func assertHomeTilesOpenDestinations(_ tiles: [Tile]) { + for tile in tiles { + goHome() + tapHomeTile(tile.id) + + guard let destinationTitle = tile.destinationTitle else { continue } + XCTAssertTrue( + destinationExists(destinationTitle, timeout: 8), + "\(tile.id) must open \(destinationTitle)" + ) + } + } + private func openOverflowMenu() { let overflow = app.buttons["OverflowBarButtonItem"] if overflow.waitForExistence(timeout: 2) { diff --git a/apps/swift/Tests/PackRatUITests/PackMacOSTests.swift b/apps/swift/Tests/PackRatUITests/PackMacOSTests.swift index a8391d6104..9631e043ec 100644 --- a/apps/swift/Tests/PackRatUITests/PackMacOSTests.swift +++ b/apps/swift/Tests/PackRatUITests/PackMacOSTests.swift @@ -184,7 +184,7 @@ final class PackMacOSTests: AppUITestCase { private func createPack(named name: String) { goToSidebar("Packs") - waitFor(app.buttons["New Pack"]).click() + waitFor(app.buttons["packs_new_pack_button"].firstMatch).click() let nameField = app.textFields["pack_name"] waitFor(nameField) diff --git a/apps/swift/Tests/PackRatUITests/PackSubFlowMacOSTests.swift b/apps/swift/Tests/PackRatUITests/PackSubFlowMacOSTests.swift index 48b8a675b3..66aaf9d99a 100644 --- a/apps/swift/Tests/PackRatUITests/PackSubFlowMacOSTests.swift +++ b/apps/swift/Tests/PackRatUITests/PackSubFlowMacOSTests.swift @@ -109,7 +109,7 @@ final class PackSubFlowMacOSTests: AppUITestCase { private func createPack(named name: String) { goToSidebar("Packs") - waitFor(app.buttons["New Pack"]).click() + waitFor(app.buttons["packs_new_pack_button"].firstMatch).click() let nameField = app.textFields["pack_name"] waitFor(nameField) nameField.click() diff --git a/apps/swift/Tests/PackRatUITests/PackTemplateTests.swift b/apps/swift/Tests/PackRatUITests/PackTemplateTests.swift index ed8a9a5854..f3d63dbb7c 100644 --- a/apps/swift/Tests/PackRatUITests/PackTemplateTests.swift +++ b/apps/swift/Tests/PackRatUITests/PackTemplateTests.swift @@ -78,12 +78,12 @@ final class PackTemplateTests: AppUITestCase { goToTab("Templates") waitFor(app.buttons["New Template"]).tap() - // Category picker should be visible + selectTemplateCategory("Backpacking") XCTAssertTrue( - app.buttons.matching(NSPredicate(format: "label CONTAINS 'Category'")).firstMatch + app.buttons.matching(NSPredicate(format: "label CONTAINS 'Backpacking'")).firstMatch .waitForExistence(timeout: 5) - || app.staticTexts["Category"].waitForExistence(timeout: 2), - "Category picker must be visible in template form" + || app.staticTexts["Backpacking"].waitForExistence(timeout: 2), + "Selected template category must be reflected in the form" ) app.buttons["Cancel"].tap() } @@ -120,6 +120,22 @@ final class PackTemplateTests: AppUITestCase { waitFor(row, timeout: 5) } + private func selectTemplateCategory(_ category: String) { + let picker = app.buttons["template_category"].firstMatch + let fallbackPicker = app.buttons.matching(NSPredicate(format: "label CONTAINS 'Category'")).firstMatch + let targetPicker = picker.waitForExistence(timeout: 2) ? picker : fallbackPicker + waitFor(targetPicker, timeout: 5, message: "Template category picker must be visible") + targetPicker.tap() + + let option = app.buttons[category].firstMatch + let optionText = app.staticTexts[category].firstMatch + if option.waitForExistence(timeout: 5) { + option.tap() + } else { + waitFor(optionText, timeout: 5, message: "Template category option '\(category)' must be visible").tap() + } + } + private func cleanupTemplate(named name: String) { // Tab navigation may be impossible if the search field is focused or // a sheet is open; wrap so cleanup never crashes the test report. diff --git a/apps/swift/Tests/PackRatUITests/PackTests.swift b/apps/swift/Tests/PackRatUITests/PackTests.swift index a036844390..772fc0411b 100644 --- a/apps/swift/Tests/PackRatUITests/PackTests.swift +++ b/apps/swift/Tests/PackRatUITests/PackTests.swift @@ -32,21 +32,19 @@ final class PackTests: AppUITestCase { } func testCreatePackWithCategory() throws { - // The createPack helper already picks Hiking as the category. - let packName = uniqueName("E2E Hiking Pack") + let packName = uniqueName("E2E Backpacking Pack") createdPackName = packName - createPack(named: packName) + createPack(named: packName, category: "Backpacking") XCTAssertTrue( app.staticTexts[packName].waitForExistence(timeout: 5), "Pack with category must appear in list" ) - // Hiking badge should be visible on the row XCTAssertTrue( - app.staticTexts["Hiking"].firstMatch.exists, - "Hiking category label must appear on the pack row" + app.staticTexts["Backpacking"].firstMatch.waitForExistence(timeout: 5), + "Selected Backpacking category label must appear on the pack row" ) } @@ -185,19 +183,41 @@ final class PackTests: AppUITestCase { // MARK: - Helpers - private func createPack(named name: String) { + private func createPack(named name: String, category: String? = nil) { goToTab("Packs") - waitFor(app.buttons["New Pack"]).tap() + waitFor(app.buttons["packs_new_pack_button"].firstMatch).tap() let nameField = app.textFields["pack_name"] waitFor(nameField) nameField.tap() nameField.typeText(name) + if let category { + selectPackCategory(category) + } + app.buttons["Create"].tap() waitFor(app.staticTexts[name], timeout: 15) } + private func selectPackCategory(_ category: String) { + let picker = app.buttons["pack_category"].firstMatch + let fallbackPicker = app.buttons.matching(NSPredicate(format: "label CONTAINS 'Category'")).firstMatch + let targetPicker = picker.waitForExistence(timeout: 2) ? picker : fallbackPicker + waitFor(targetPicker, timeout: 5, message: "Pack category picker must be visible") + targetPicker.tap() + + let option = app.buttons[category].firstMatch + let optionText = app.staticTexts[category].firstMatch + if option.waitForExistence(timeout: 5) { + option.tap() + } else { + waitFor(optionText, timeout: 5, message: "Pack category option '\(category)' must be visible").tap() + } + + app.navigationBars["Category"].buttons.element(boundBy: 0).tapIfExists() + } + private func openPack(named name: String) { goToTab("Packs") let cell = waitFor(app.staticTexts[name]) diff --git a/apps/swift/Tests/PackRatUITests/ScreenshotSmokeTests.swift b/apps/swift/Tests/PackRatUITests/ScreenshotSmokeTests.swift index a2979e6fab..b7c64f64a8 100644 --- a/apps/swift/Tests/PackRatUITests/ScreenshotSmokeTests.swift +++ b/apps/swift/Tests/PackRatUITests/ScreenshotSmokeTests.swift @@ -6,23 +6,23 @@ import XCTest /// are attached to the `.xcresult`; host-side PNG capture can be done with /// `xcrun simctl io screenshot`. final class ScreenshotSmokeTests: AppUITestCase { + override var additionalLaunchArguments: [String] { ["--ui-test-fixtures"] } + func testCaptureCoreScreens() throws { capture("02-home") goToDestination("Packs") - XCTAssertTrue(app.navigationBars["Packs"].waitForExistence(timeout: 8)) + assertDestinationLoaded("Packs") capture("03-packs") goToDestination("Weather") - XCTAssertTrue(app.navigationBars["Weather"].waitForExistence(timeout: 8)) - let searchField = app.textFields["Search locations..."].exists - ? app.textFields["Search locations..."] - : app.textFields["Search locations…"] + assertDestinationLoaded("Weather") + let searchField = app.searchFields["Search locations…"] XCTAssertTrue(searchField.waitForExistence(timeout: 10)) searchField.tap() searchField.typeText("Denver") let firstResult = app.buttons.matching( - NSPredicate(format: "label CONTAINS 'Denver' AND label CONTAINS ','") + NSPredicate(format: "identifier BEGINSWITH 'weather_search_result_' AND label CONTAINS 'Denver'") ).firstMatch XCTAssertTrue(firstResult.waitForExistence(timeout: 10)) firstResult.tap() @@ -48,4 +48,19 @@ final class ScreenshotSmokeTests: AppUITestCase { goToTab(label) #endif } + + private func assertDestinationLoaded(_ label: String, file: StaticString = #filePath, line: UInt = #line) { + #if os(macOS) + switch label { + case "Packs": + XCTAssertTrue(app.buttons["New Pack"].waitForExistence(timeout: 8), file: file, line: line) + case "Weather": + XCTAssertTrue(app.searchFields["Search locations…"].waitForExistence(timeout: 8), file: file, line: line) + default: + XCTAssertTrue(app.staticTexts[label].waitForExistence(timeout: 8), file: file, line: line) + } + #else + XCTAssertTrue(app.navigationBars[label].waitForExistence(timeout: 8), file: file, line: line) + #endif + } } diff --git a/apps/swift/Tests/PackRatUITests/SeasonSuggestionsMacOSTests.swift b/apps/swift/Tests/PackRatUITests/SeasonSuggestionsMacOSTests.swift index 2792afd9a3..f53881dbf4 100644 --- a/apps/swift/Tests/PackRatUITests/SeasonSuggestionsMacOSTests.swift +++ b/apps/swift/Tests/PackRatUITests/SeasonSuggestionsMacOSTests.swift @@ -55,5 +55,34 @@ final class SeasonSuggestionsMacOSTests: AppUITestCase { app.buttons["Done"].tapIfExists() } + + func testGetSuggestionsReturnsDeterministicResults() { + goToSidebar("Home") + + let tile = app.buttons["home_action_seasonsuggestions"] + guard tile.waitForExistence(timeout: 8) else { + XCTFail("Season Suggestions tile not found on Home") + return + } + tile.click() + + let locationField = app.textFields["season_suggestions_location"] + waitFor(locationField, timeout: 5, message: "Season Suggestions location field must be visible") + locationField.click() + locationField.typeText("Yosemite") + + let getButton = app.buttons["season_suggestions_submit"] + waitFor(getButton, timeout: 5) + XCTAssertTrue(getButton.isEnabled, "Get Suggestions must enable when location is entered") + getButton.click() + + XCTAssertTrue( + app.descendants(matching: .any)["season_suggestions_results"].waitForExistence(timeout: 20) + || app.staticTexts["Shoulder Season Overnight"].waitForExistence(timeout: 2), + "Season Suggestions must render deterministic E2E results" + ) + + app.buttons["Done"].tapIfExists() + } } #endif diff --git a/apps/swift/Tests/PackRatUITests/SeasonSuggestionsTests.swift b/apps/swift/Tests/PackRatUITests/SeasonSuggestionsTests.swift index 1b57a58c39..0ef40eaedc 100644 --- a/apps/swift/Tests/PackRatUITests/SeasonSuggestionsTests.swift +++ b/apps/swift/Tests/PackRatUITests/SeasonSuggestionsTests.swift @@ -42,6 +42,28 @@ final class SeasonSuggestionsTests: AppUITestCase { app.buttons["Done"].tapIfExists() } + + func testGetSuggestionsReturnsDeterministicResults() { + goToHomeAction("Season Suggestions") + + let locationField = app.textFields["season_suggestions_location"] + waitFor(locationField, timeout: 5, message: "Season Suggestions location field must be visible") + locationField.tap() + locationField.typeText("Yosemite") + + let getButton = app.buttons["season_suggestions_submit"] + waitFor(getButton, timeout: 5) + XCTAssertTrue(getButton.isEnabled, "Get Suggestions must enable when location is entered") + getButton.tap() + + XCTAssertTrue( + app.descendants(matching: .any)["season_suggestions_results"].waitForExistence(timeout: 20) + || app.staticTexts["Shoulder Season Overnight"].waitForExistence(timeout: 2), + "Season Suggestions must render deterministic E2E results" + ) + + app.buttons["Done"].tapIfExists() + } } #endif diff --git a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift index 5b04cdec1d..cc51031cd7 100644 --- a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift +++ b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift @@ -1,3 +1,4 @@ +import CryptoKit import XCTest final class VisualScreenshotTests: XCTestCase { @@ -113,6 +114,20 @@ final class VisualScreenshotTests: XCTestCase { } #endif + func testGuestAccountLimitVisualSurface() throws { + enterGuestMode() + + #if os(iOS) + if isPadVisualRun { + captureSidebarGuestAccountLimits() + } else { + capturePhoneGuestAccountLimits() + } + #elseif os(macOS) + captureSidebarGuestAccountLimits() + #endif + } + func testAuthenticatedVisualSurface() throws { launchAuthenticated() capture("20-auth-home") @@ -404,7 +419,14 @@ final class VisualScreenshotTests: XCTestCase { let prefix = mode.prefix let suffix = mode.suffix for action in actions { - captureHomeAction(action.title, name: "\(prefix)-\(action.slug)\(suffix)") + let name = "\(prefix)-\(action.slug)\(suffix)" + if mode == .sampleData, action.title == "Season Suggestions" { + captureHomeAction(action.title, name: name, dismissAfterCapture: false) + captureSeasonSuggestionsResult(name: "\(prefix)-season-suggestions-results\(suffix)") + dismissPhoneDestination() + } else { + captureHomeAction(action.title, name: name) + } } } @@ -766,6 +788,25 @@ final class VisualScreenshotTests: XCTestCase { dismissPhoneDestination() } + private func captureGuestLimitedTab(_ label: String, name: String) { + captureTab(label, name: name) + assertExpectedAccountRequiredState(for: name) + } + + private func capturePhoneGuestAccountLimits() { + captureGuestLimitedTab("Assistant", name: "50-guest-limit-assistant") + captureGuestLimitedHomeAction("AI Packs", name: "50-guest-limit-ai-packs") + captureGuestLimitedHomeAction("Catalog", name: "50-guest-limit-catalog") + captureGuestLimitedHomeAction("Weather", name: "50-guest-limit-weather") + + if UITestFeatureFlags.enableFeed { + captureGuestLimitedHomeAction("Community Feed", name: "50-guest-limit-feed") + } + if UITestFeatureFlags.enableWildlifeIdentification { + captureGuestLimitedHomeAction("Wildlife ID", name: "50-guest-limit-wildlife") + } + } + private func dismissPhoneDestination() { if app.buttons["Done"].exists { app.buttons["Done"].tap() @@ -816,7 +857,14 @@ final class VisualScreenshotTests: XCTestCase { } if scope != .primary { - captureMacHomeAction("Season Suggestions", name: "\(prefix)-season-suggestions\(suffix)") + let name = "\(prefix)-season-suggestions\(suffix)" + if mode == .sampleData { + captureMacHomeAction("Season Suggestions", name: name, dismissAfterCapture: false) + captureSeasonSuggestionsResult(name: "\(prefix)-season-suggestions-results\(suffix)") + dismissPresentedSurface() + } else { + captureMacHomeAction("Season Suggestions", name: name) + } } } @@ -873,6 +921,32 @@ final class VisualScreenshotTests: XCTestCase { } } + private func captureSidebarGuestAccountLimits() { + let entries = [ + ("Assistant", "50-guest-limit-assistant"), + ("Weather", "50-guest-limit-weather"), + ("Catalog", "50-guest-limit-catalog"), + ("AI Packs", "50-guest-limit-ai-packs"), + ] + + for (label, name) in entries { + selectSidebar(label) + capture(name) + assertExpectedAccountRequiredState(for: name) + } + + if UITestFeatureFlags.enableFeed { + selectSidebar("Feed") + capture("50-guest-limit-feed") + assertExpectedAccountRequiredState(for: "50-guest-limit-feed") + } + if UITestFeatureFlags.enableWildlifeIdentification { + selectSidebar("Wildlife") + capture("50-guest-limit-wildlife") + assertExpectedAccountRequiredState(for: "50-guest-limit-wildlife") + } + } + private func captureMacExpandedPackStates() { resetMacSampleDataSidebar("Packs") capture("81-data-pack-detail-expanded") @@ -991,6 +1065,27 @@ final class VisualScreenshotTests: XCTestCase { } XCTFail("Expected Home action '\(title)' for screenshot \(name)") } + + private func captureSeasonSuggestionsResult(name: String) { + let field = app.textFields["season_suggestions_location"].firstMatch + XCTAssertTrue(field.waitForExistence(timeout: 5), "Expected Season Suggestions location field for \(name)") + activate(field) + field.typeText("Leavenworth, WA") + + let submit = app.buttons["season_suggestions_submit"].firstMatch + XCTAssertTrue(submit.waitForExistence(timeout: 5), "Expected Season Suggestions submit button for \(name)") + activate(submit) + + let results = app.descendants(matching: .any) + .matching(identifier: "season_suggestions_results") + .firstMatch + let deterministicTitle = app.staticTexts["Shoulder Season Overnight"] + XCTAssertTrue( + results.waitForExistence(timeout: 20) || deterministicTitle.waitForExistence(timeout: 2), + "Expected Season Suggestions results for \(name)" + ) + capture(name) + } #endif private func captureGlobalSearch(name: String, query: String? = nil) { @@ -1186,6 +1281,7 @@ final class VisualScreenshotTests: XCTestCase { let email = (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_EMAIL") as? String) ?? "e2e@packrat.test" let userId = ProcessInfo.processInfo.environment["E2E_TEST_USER_ID"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_USER_ID") as? String) ?? "00000000-0000-4000-8000-000000000001" app.terminate() @@ -1197,8 +1293,10 @@ final class VisualScreenshotTests: XCTestCase { "--seed-e2e-auth", ] app.launchEnvironment["PACKRAT_VISUAL_SCREENSHOTS"] = "1" + app.launchEnvironment["E2E_API_BASE_URL"] = visualE2EAPIBaseURL app.launchEnvironment["PACKRAT_E2E_EMAIL"] = email app.launchEnvironment["PACKRAT_E2E_USER_ID"] = userId + app.launchEnvironment["PACKRAT_E2E_SESSION_TOKEN"] = visualE2ESessionToken(email: email, userId: userId) if sampleData { app.launchArguments.append("--visual-sample-data") app.launchEnvironment["PACKRAT_VISUAL_SAMPLE_DATA"] = "1" @@ -1239,6 +1337,7 @@ final class VisualScreenshotTests: XCTestCase { app.launchArguments.append("--force-offline") } app.launchEnvironment["PACKRAT_VISUAL_SCREENSHOTS"] = "1" + app.launchEnvironment["E2E_API_BASE_URL"] = visualE2EAPIBaseURL app.launch() #if os(macOS) app.activate() @@ -1246,6 +1345,28 @@ final class VisualScreenshotTests: XCTestCase { #endif } + private var visualE2EAPIBaseURL: String { + ProcessInfo.processInfo.environment["E2E_API_BASE_URL"] ?? "http://localhost:8787" + } + + private func visualE2ESessionToken(email: String, userId: String) -> String { + if let token = ProcessInfo.processInfo.environment["PACKRAT_E2E_SESSION_TOKEN"], !token.isEmpty { + return token + } + let bundle = Bundle(for: VisualScreenshotTests.self) + if let token = bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_SESSION_TOKEN") as? String, + !token.isEmpty { + return token + } + + let secret = ProcessInfo.processInfo.environment["BETTER_AUTH_SECRET"] + ?? "e2e-better-auth-secret-at-least-32-chars" + let material = "\(secret):\(email.lowercased()):\(userId)" + let digest = SHA256.hash(data: Data(material.utf8)) + let hex = digest.map { String(format: "%02x", $0) }.joined() + return "e2e-local.\(hex)" + } + private func restartLoggedOut() { app.terminate() launchLoggedOut() diff --git a/apps/swift/project.yml b/apps/swift/project.yml index 344dd24e47..02eec57648 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -149,53 +149,13 @@ targets: - Sources/PackRatShared - path: Resources/Assets.xcassets buildPhase: resources - postbuildScripts: - - name: Copy Watch App Store Icons - basedOnDependencyAnalysis: false - script: | - set -euo pipefail - ICON_DIR="${SRCROOT}/Resources/Assets.xcassets/AppIcon.appiconset" - APP_DIR="${TARGET_BUILD_DIR}/${CONTENTS_FOLDER_PATH}" - for icon in \ - AppIcon-watch-notification-24@2x.png \ - AppIcon-watch-notification-27.5@2x.png \ - AppIcon-watch-settings-29@2x.png \ - AppIcon-watch-settings-29@3x.png \ - AppIcon-watch-launcher-40@2x.png \ - AppIcon-watch-launcher-44@2x.png \ - AppIcon-watch-launcher-50@2x.png \ - AppIcon-watch-quicklook-44@2x.png \ - AppIcon-watch-quicklook-50@2x.png \ - AppIcon-watch-quicklook-86@2x.png \ - AppIcon-watch-quicklook-98@2x.png \ - AppIcon-watch-quicklook-108@2x.png \ - AppIcon-watch-longlook-44x44@2x.png \ - AppIcon-watch-longlook-50x50@2x.png \ - AppIcon-watch-marketing-1024.png - do - cp "${ICON_DIR}/${icon}" "${APP_DIR}/${icon}" - done + - path: Resources/WatchAssets.xcassets + buildPhase: resources info: path: Resources/Info-watchOS.plist properties: CFBundleDisplayName: PackRat Swift - CFBundleIconName: AppIcon - CFBundleIconFiles: - - AppIcon-watch-notification-24@2x.png - - AppIcon-watch-notification-27.5@2x.png - - AppIcon-watch-settings-29@2x.png - - AppIcon-watch-settings-29@3x.png - - AppIcon-watch-launcher-40@2x.png - - AppIcon-watch-launcher-44@2x.png - - AppIcon-watch-launcher-50@2x.png - - AppIcon-watch-quicklook-44@2x.png - - AppIcon-watch-quicklook-50@2x.png - - AppIcon-watch-quicklook-86@2x.png - - AppIcon-watch-quicklook-98@2x.png - - AppIcon-watch-quicklook-108@2x.png - - AppIcon-watch-longlook-44x44@2x.png - - AppIcon-watch-longlook-50x50@2x.png - - AppIcon-watch-marketing-1024.png + CFBundleIconName: WatchAppIcon CFBundleShortVersionString: "2.0.29" CFBundleVersion: "2026071601" WKApplication: true @@ -210,7 +170,7 @@ targets: DEVELOPMENT_TEAM: 666HGMV2LU PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.watchkitapp PRODUCT_MODULE_NAME: PackRatWatch - ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon + ASSETCATALOG_COMPILER_APPICON_NAME: WatchAppIcon PackRat-macOS: type: application diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index 89fe1e07e2..6ebdbce16f 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -1,5 +1,6 @@ #!/usr/bin/env bun import { spawn, spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; import { cpSync, existsSync, @@ -29,6 +30,7 @@ import { oneOrMore, } from 'magic-regexp'; import { z } from 'zod'; +import { ensureLocalE2EAPI } from './lib/e2e-api'; import { formatSummaryLine, readSummary, type TestSummary, XcResultError } from './lib/xcresult'; type Platform = 'ios' | 'ipad' | 'macos' | 'watch'; @@ -59,6 +61,7 @@ type VisualTestResult = { type PlatformRunSummary = { platform: Platform; screenshotDir: string; + screenshotCount: number; coverageManifest: string; contactSheet: string; groupedContactSheets: string[]; @@ -70,6 +73,14 @@ const REPO_ROOT = resolve(import.meta.dir, '../../..'); const SWIFT_DIR = resolve(REPO_ROOT, 'apps/swift'); const RESULTS_DIR = resolve(SWIFT_DIR, 'TestResults'); const DEFAULT_OUT_DIR = resolve(REPO_ROOT, 'artifacts/screenshots'); +const IOS_SCHEME_PATH = resolve( + SWIFT_DIR, + 'PackRat.xcodeproj/xcshareddata/xcschemes/PackRat-iOS.xcscheme', +); +const MACOS_SCHEME_PATH = resolve( + SWIFT_DIR, + 'PackRat.xcodeproj/xcshareddata/xcschemes/PackRat-macOS.xcscheme', +); const EMAIL_RE = createRegExp( oneOrMore(charIn('A-Z0-9._%+-')), '@', @@ -101,6 +112,7 @@ const CHROME_CANDIDATES = [ '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser', ]; const FEATURE_FLAGS = APP_CONFIG.featureFlags; +const API_E2E_ENV_PATH = resolve(REPO_ROOT, 'packages/api/.dev.vars.e2e'); const IOS_SURFACES = [ 'packs', 'trips', @@ -249,7 +261,7 @@ Captures guest and authenticated visual surfaces through VisualScreenshotTests a } function parseArgs(argv: readonly string[]): Options { - let platforms: Platform[] = ['ios', 'ipad', 'macos']; + let platforms: Platform[] = ['ios', 'ipad', 'macos', 'watch']; let outDir = DEFAULT_OUT_DIR; let skipTests = false; @@ -263,7 +275,7 @@ function parseArgs(argv: readonly string[]): Options { } if (arg === '--platform') { const value = argv[++i]; - if (!value) throw new Error('--platform requires ios, ipad, macos, or both'); + if (!value) throw new Error('--platform requires ios, ipad, macos, watch, both, or all'); platforms = parsePlatforms(value); continue; } @@ -289,7 +301,7 @@ function parseArgs(argv: readonly string[]): Options { function parsePlatforms(value: string): Platform[] { const normalized = value.toLowerCase(); - if (normalized === 'both') return ['ios', 'ipad', 'macos']; + if (normalized === 'both') return ['ios', 'ipad', 'macos', 'watch']; if (normalized === 'all') return ['ios', 'ipad', 'macos', 'watch']; if (normalized === 'ios') return ['ios']; if (normalized === 'ipad') return ['ipad']; @@ -912,6 +924,11 @@ function runXcodeVisualTest(platform: Platform, screenshotDir: string): Promise< const resultBundle = allocateResultBundle(platform); const writableScreenshotDir = allocateWritableScreenshotDir(platform); const credentials = e2eBuildSettings(); + injectVisualSchemeEnvironment(platform, { + PACKRAT_SCREENSHOT_DIR: writableScreenshotDir, + PACKRAT_VISUAL_PLATFORM: platform, + ...buildSettingsToEnv(credentials), + }); const commonArgs = [ 'test', '-resultBundlePath', @@ -1019,6 +1036,78 @@ function runXcodeVisualTest(platform: Platform, screenshotDir: string): Promise< }); } +function injectVisualSchemeEnvironment(platform: Platform, env: Record): void { + const schemePath = platform === 'macos' ? MACOS_SCHEME_PATH : IOS_SCHEME_PATH; + if (!existsSync(schemePath)) return; + + let content = readFileSync(schemePath, 'utf8'); + content = removeEnvironmentVariablesBlock(content); + content = content.replace( + 'shouldUseLaunchSchemeArgsEnv = "YES"', + 'shouldUseLaunchSchemeArgsEnv = "NO"', + ); + + const variables = Object.entries(env) + .filter(([, value]) => value.length > 0) + .map(([key, value]) => environmentVariableXml(key, value)); + if (variables.length === 0) { + writeFileSync(schemePath, content); + return; + } + + const block = [ + ' ', + ...variables, + ' ', + '', + ].join('\n'); + writeFileSync(schemePath, content.replace(' ', `${block} `)); +} + +function buildSettingsToEnv(settings: string[]): Record { + const env: Record = {}; + for (const setting of settings) { + const equals = setting.indexOf('='); + if (equals <= 0) continue; + env[setting.slice(0, equals)] = setting.slice(equals + 1); + } + return env; +} + +function environmentVariableXml(key: string, value: string): string { + return [ + ' ', + ' ', + ].join('\n'); +} + +function removeEnvironmentVariablesBlock(content: string): string { + let output = content; + while (true) { + const start = output.indexOf(''); + if (start === -1) return output; + const end = output.indexOf('', start); + if (end === -1) return output; + const removalStart = output.lastIndexOf('\n', start); + const removalEnd = end + ''.length; + output = `${output.slice(0, removalStart === -1 ? start : removalStart)}${output.slice(removalEnd)}`; + } +} + +function escapeXml(s: string): string { + return Array.from(s, (char) => { + if (char === '&') return '&'; + if (char === '<') return '<'; + if (char === '>') return '>'; + if (char === '"') return '"'; + if (char === "'") return '''; + return char; + }).join(''); +} + async function runWatchVisualCapture(screenshotDir: string): Promise { const destination = pickAvailableWatchDestination(); const deviceId = destination.deviceId; @@ -1061,11 +1150,11 @@ async function runWatchVisualCapture(screenshotDir: string): Promise { + if (!existsSync(path)) return {}; + const vars: Record = {}; + for (const rawLine of readFileSync(path, 'utf8').replaceAll('\r\n', '\n').split('\n')) { + const line = rawLine.trim(); + if (!line || line.startsWith('#') || !line.includes('=')) continue; + const index = line.indexOf('='); + const key = line.slice(0, index).trim(); + let value = line.slice(index + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + vars[key] = value; + } + return vars; } function assertAutomationModeAvailable(): void { @@ -1879,49 +2015,70 @@ async function main() { loadDotEnv(); const options = parseArgs(process.argv.slice(2)); mkdirSync(options.outDir, { recursive: true }); - const runSummary: PlatformRunSummary[] = []; - - for (const platform of options.platforms) { - const dir = screenshotDirFor(options.outDir, platform); - let testResult: VisualTestResult | null = null; - mkdirSync(dir, { recursive: true }); - if (!options.skipTests) { - rmSync(dir, { recursive: true, force: true }); + const runSummaryPath = resolve(options.outDir, 'run-summary.json'); + const runSummaryByPlatform = readExistingRunSummary(runSummaryPath); + addExistingArtifactSummaries(options.outDir, runSummaryByPlatform); + const shouldEnsureAPI = + !options.skipTests && options.platforms.some((platform) => platform !== 'watch'); + const apiHandle = shouldEnsureAPI + ? await ensureLocalE2EAPI({ + packratEnv: Bun.env.PACKRAT_ENV ?? nodeEnv.PACKRAT_ENV ?? 'local', + env: Bun.env as NodeJS.ProcessEnv, + }) + : null; + + try { + for (const platform of options.platforms) { + const dir = screenshotDirFor(options.outDir, platform); + let testResult: VisualTestResult | null = null; mkdirSync(dir, { recursive: true }); - testResult = await runXcodeVisualTest(platform, dir); - } - validateScreenshotMatrix(platform, dir); - const contactSheet = await renderContactSheet(platform, options.outDir); - const groupedContactSheets = await renderGroupedContactSheets(platform, options.outDir); - const coverageManifest = resolve(dir, 'coverage-manifest.json'); - runSummary.push({ - platform, - screenshotDir: dir, - coverageManifest, - contactSheet, - groupedContactSheets, - ...(testResult - ? { - resultBundle: testResult.resultBundle, - ...(testResult.summary ? { testSummary: testResult.summary } : {}), - } - : {}), - }); - console.log(`✓ ${platform} contact sheet: ${contactSheet}`); - for (const groupedContactSheet of groupedContactSheets) { - console.log(`✓ ${platform} grouped contact sheet: ${groupedContactSheet}`); + if (!options.skipTests) { + rmSync(dir, { recursive: true, force: true }); + mkdirSync(dir, { recursive: true }); + testResult = await runXcodeVisualTest(platform, dir); + } + validateScreenshotMatrix(platform, dir); + const contactSheet = await renderContactSheet(platform, options.outDir); + const groupedContactSheets = await renderGroupedContactSheets(platform, options.outDir); + const coverageManifest = resolve(dir, 'coverage-manifest.json'); + const screenshotCount = countCapturedScreenshots(dir); + const existingSummary = runSummaryByPlatform.get(platform); + runSummaryByPlatform.set(platform, { + platform, + screenshotDir: dir, + screenshotCount, + coverageManifest, + contactSheet, + groupedContactSheets, + ...(testResult + ? { + resultBundle: testResult.resultBundle, + ...(testResult.summary ? { testSummary: testResult.summary } : {}), + } + : { + ...(existingSummary?.resultBundle + ? { resultBundle: existingSummary.resultBundle } + : {}), + ...(existingSummary?.testSummary ? { testSummary: existingSummary.testSummary } : {}), + }), + }); + console.log(`✓ ${platform} contact sheet: ${contactSheet}`); + for (const groupedContactSheet of groupedContactSheets) { + console.log(`✓ ${platform} grouped contact sheet: ${groupedContactSheet}`); + } + console.log(`✓ ${platform} coverage manifest: ${coverageManifest}`); } - console.log(`✓ ${platform} coverage manifest: ${coverageManifest}`); + } finally { + await apiHandle?.stop(); } - const runSummaryPath = resolve(options.outDir, 'run-summary.json'); writeFileSync( runSummaryPath, `${safeJsonStringify( { generatedAt: new Date().toISOString(), skipTests: options.skipTests, - platforms: runSummary, + platforms: [...runSummaryByPlatform.values()], }, null, 2, @@ -1930,6 +2087,83 @@ async function main() { console.log(`✓ screenshot run summary: ${runSummaryPath}`); } +function readExistingRunSummary(path: string): Map { + const summaries = new Map(); + if (!existsSync(path)) return summaries; + try { + const parsed = safeJsonParse<{ platforms?: unknown[] }>(readFileSync(path, 'utf8'), { + strict: true, + }); + for (const candidate of parsed.platforms ?? []) { + const summary = parsePlatformRunSummary(candidate); + if (summary) summaries.set(summary.platform, summary); + } + } catch {} + return summaries; +} + +function addExistingArtifactSummaries( + outDir: string, + summaries: Map, +): void { + for (const platform of ['ios', 'ipad', 'macos', 'watch'] satisfies Platform[]) { + if (summaries.has(platform)) continue; + const screenshotDir = screenshotDirFor(outDir, platform); + const coverageManifest = resolve(screenshotDir, 'coverage-manifest.json'); + const contactSheet = resolve(outDir, `${platform}-contact-sheet.png`); + if (!existsSync(coverageManifest) || !existsSync(contactSheet)) continue; + const groupedContactSheets = CONTACT_SHEET_GROUPS.map((group) => + resolve(outDir, `${platform}-contact-sheet-${group.suffix}.png`), + ).filter((path) => existsSync(path)); + summaries.set(platform, { + platform, + screenshotDir, + screenshotCount: countCapturedScreenshots(screenshotDir), + coverageManifest, + contactSheet, + groupedContactSheets, + }); + } +} + +function parsePlatformRunSummary(value: unknown): PlatformRunSummary | null { + if (!value || typeof value !== 'object') return null; + const candidate = value as Record; + if (!isPlatform(candidate.platform)) return null; + if ( + typeof candidate.screenshotDir !== 'string' || + typeof candidate.coverageManifest !== 'string' || + typeof candidate.contactSheet !== 'string' || + !Array.isArray(candidate.groupedContactSheets) || + !candidate.groupedContactSheets.every((entry) => typeof entry === 'string') + ) { + return null; + } + + return { + platform: candidate.platform, + screenshotDir: candidate.screenshotDir, + screenshotCount: + typeof candidate.screenshotCount === 'number' && Number.isFinite(candidate.screenshotCount) + ? candidate.screenshotCount + : countCapturedScreenshots(candidate.screenshotDir), + coverageManifest: candidate.coverageManifest, + contactSheet: candidate.contactSheet, + groupedContactSheets: candidate.groupedContactSheets, + ...(typeof candidate.resultBundle === 'string' ? { resultBundle: candidate.resultBundle } : {}), + ...(candidate.testSummary ? { testSummary: candidate.testSummary as TestSummary } : {}), + }; +} + +function isPlatform(value: unknown): value is Platform { + return value === 'ios' || value === 'ipad' || value === 'macos' || value === 'watch'; +} + +function countCapturedScreenshots(screenshotDir: string): number { + if (!existsSync(screenshotDir)) return 0; + return readdirSync(screenshotDir).filter((fileName) => fileName.endsWith('.png')).length; +} + main() .then(() => process.exit(0)) .catch((err) => { diff --git a/apps/swift/scripts/lib/e2e-api.ts b/apps/swift/scripts/lib/e2e-api.ts new file mode 100644 index 0000000000..47b6f8c33a --- /dev/null +++ b/apps/swift/scripts/lib/e2e-api.ts @@ -0,0 +1,114 @@ +import { type ChildProcess, spawn } from 'node:child_process'; +import { resolve } from 'node:path'; + +type LocalAPIHandle = { + baseURL: string; + stop: () => Promise; +}; + +const REPO_ROOT = resolve(import.meta.dir, '../../../..'); +const API_DIR = resolve(REPO_ROOT, 'packages/api'); + +function timeoutSignal(ms: number): AbortSignal { + const controller = new AbortController(); + setTimeout(() => controller.abort(), ms).unref(); + return controller.signal; +} + +async function isHealthy(baseURL: string): Promise { + try { + const response = await fetch(new URL('/health', baseURL), { + signal: timeoutSignal(1000), + }); + return response.ok; + } catch { + return false; + } +} + +async function waitForHealthy(baseURL: string, child: ChildProcess): Promise { + const startedAt = Date.now(); + let exited = false; + let exitCode: number | null = null; + child.once('exit', (code) => { + exited = true; + exitCode = code; + }); + + while (Date.now() - startedAt < 120_000) { + if (await isHealthy(baseURL)) return; + if (exited) { + throw new Error( + `Local E2E API exited before becoming healthy (code ${exitCode ?? 'unknown'}).`, + ); + } + await Bun.sleep(1000); + } + + throw new Error(`Local E2E API did not become healthy at ${baseURL}/health within 120s.`); +} + +async function stopChild(child: ChildProcess): Promise { + if (child.exitCode !== null || child.signalCode !== null) return; + await new Promise((resolve) => { + const timeout = setTimeout(() => { + if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL'); + resolve(); + }, 5000); + timeout.unref(); + child.once('exit', () => { + clearTimeout(timeout); + resolve(); + }); + child.kill('SIGTERM'); + }); +} + +export async function ensureLocalE2EAPI(input: { + packratEnv: string; + env: NodeJS.ProcessEnv; +}): Promise { + const explicitBaseURL = input.env.E2E_API_BASE_URL; + const defaultPort = input.packratEnv === 'dev-local' ? '8791' : input.env.PORT || '8787'; + const baseURL = explicitBaseURL?.trim() || `http://localhost:${defaultPort}`; + const shouldUseLocalAPI = input.packratEnv === 'local' || input.packratEnv === 'dev-local'; + const skipStart = input.env.PACKRAT_SWIFT_E2E_SKIP_API_START === '1'; + + if (!shouldUseLocalAPI) { + return { baseURL, stop: async () => {} }; + } + + if (await isHealthy(baseURL)) { + console.log(`✓ Local E2E API is healthy at ${baseURL}`); + return { baseURL, stop: async () => {} }; + } + + if (explicitBaseURL || skipStart) { + throw new Error( + `Local E2E API is not healthy at ${baseURL}/health. Start it with \`bun run --cwd packages/api dev:e2e\` or unset E2E_API_BASE_URL so the runner can own it.`, + ); + } + + console.log(`→ Starting local E2E API at ${baseURL}`); + const child = spawn('bun', ['run', 'dev:e2e'], { + cwd: API_DIR, + env: input.env, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + child.stdout?.on('data', (chunk) => process.stdout.write(chunk)); + child.stderr?.on('data', (chunk) => process.stderr.write(chunk)); + + try { + await waitForHealthy(baseURL, child); + console.log(`✓ Local E2E API is healthy at ${baseURL}`); + } catch (error) { + await stopChild(child); + throw error; + } + + return { + baseURL, + stop: () => stopChild(child), + }; +} diff --git a/apps/swift/scripts/run-e2e-macos.ts b/apps/swift/scripts/run-e2e-macos.ts index 8598ce489b..86c24feb5f 100644 --- a/apps/swift/scripts/run-e2e-macos.ts +++ b/apps/swift/scripts/run-e2e-macos.ts @@ -34,6 +34,7 @@ import { oneOrMore, } from 'magic-regexp'; import { ArgsError } from './lib/args'; +import { ensureLocalE2EAPI } from './lib/e2e-api'; import { normalizeMacOSTestSelectors, parseMacOSArgs } from './lib/macos-args'; import { formatSummaryLine, readSummary, XcResultError } from './lib/xcresult'; @@ -87,15 +88,19 @@ if (!E2E_EMAIL || !E2E_PASSWORD) { process.exit(1); } const PACKRAT_ENV = process.env.PACKRAT_ENV || 'local'; -const localE2ESessionToken = deriveLocalE2ESessionToken(); -const uiTestEmail = process.env.E2E_TEST_EMAIL ?? E2E_EMAIL; -const uiTestPassword = process.env.E2E_TEST_PASSWORD ?? E2E_PASSWORD; if (!existsSync(SCHEME_PATH)) { console.error(`❌ Scheme not found at ${SCHEME_PATH} — run 'bun swift' first`); process.exit(1); } +const localAPI = await ensureLocalE2EAPI({ packratEnv: PACKRAT_ENV, env: process.env }); +loadEnvFile(resolve(REPO_ROOT, 'packages/api/.dev.vars.e2e'), true); + +const localE2ESessionToken = deriveLocalE2ESessionToken(); +const uiTestEmail = process.env.E2E_TEST_EMAIL ?? E2E_EMAIL; +const uiTestPassword = process.env.E2E_TEST_PASSWORD ?? E2E_PASSWORD; + function assertAutomationModeAvailable(): void { const result = spawnSync('automationmodetool', ['help'], { encoding: 'utf8', @@ -128,12 +133,14 @@ function escapeXml(s: string): string { } function deriveLocalE2ESessionToken(): string | undefined { - const dbUrl = process.env.NEON_DATABASE_URL ?? ''; - const secret = process.env.BETTER_AUTH_SECRET; + if (PACKRAT_ENV !== 'local' && PACKRAT_ENV !== 'dev-local') return undefined; + const secret = + process.env.PACKRAT_AUTH_SECRET ?? + process.env.BETTER_AUTH_SECRET ?? + 'e2e-better-auth-secret-at-least-32-chars'; const email = process.env.E2E_TEST_EMAIL?.toLowerCase(); const userId = process.env.E2E_TEST_USER_ID; - if (!(dbUrl.includes('127.0.0.1') || dbUrl.includes('localhost'))) return undefined; - if (!secret || !email || !userId) return undefined; + if (!email || !userId) return undefined; const digest = createHash('sha256').update([secret, email, userId].join(':')).digest('hex'); return `e2e-local.${digest}`; } @@ -284,41 +291,43 @@ function redactSecrets(output: string): string { return redacted; } -const resultStatus = await new Promise((resolve) => { - const child = spawn('xcodebuild', args, { - cwd: SWIFT_DIR, - env: process.env, - }); +let exitStatus = 1; +try { + const resultStatus = await new Promise((resolve) => { + const child = spawn('xcodebuild', args, { + cwd: SWIFT_DIR, + env: process.env, + }); - child.stdout.on('data', (chunk) => { - process.stdout.write(redactSecrets(chunk.toString())); - }); - child.stderr.on('data', (chunk) => { - process.stderr.write(redactSecrets(chunk.toString())); + child.stdout.on('data', (chunk) => { + process.stdout.write(redactSecrets(chunk.toString())); + }); + child.stderr.on('data', (chunk) => { + process.stderr.write(redactSecrets(chunk.toString())); + }); + child.on('close', (code) => resolve(code)); }); - child.on('close', (code) => resolve(code)); -}); - -const result = { - status: resultStatus, -}; + exitStatus = resultStatus ?? 1; -try { - const summary = readSummary(resultBundle); - console.log(''); - console.log(formatSummaryLine(summary)); - if (summary.failingTests.length > 0) { - console.log(' Failing tests:'); - for (const t of summary.failingTests) { - console.log(` • ${t.identifier}`); + try { + const summary = readSummary(resultBundle); + console.log(''); + console.log(formatSummaryLine(summary)); + if (summary.failingTests.length > 0) { + console.log(' Failing tests:'); + for (const t of summary.failingTests) { + console.log(` • ${t.identifier}`); + } + } + } catch (err) { + if (err instanceof XcResultError) { + console.error(`⚠️ ${err.message}`); + } else { + throw err; } } -} catch (err) { - if (err instanceof XcResultError) { - console.error(`⚠️ ${err.message}`); - } else { - throw err; - } +} finally { + await localAPI.stop(); } -process.exit(result.status ?? 1); +process.exit(exitStatus); diff --git a/apps/swift/scripts/run-e2e.ts b/apps/swift/scripts/run-e2e.ts index 94e6d11499..5c03ea5db4 100644 --- a/apps/swift/scripts/run-e2e.ts +++ b/apps/swift/scripts/run-e2e.ts @@ -32,6 +32,7 @@ import { oneOrMore, } from 'magic-regexp'; import { ArgsError, parseArgs } from './lib/args'; +import { ensureLocalE2EAPI } from './lib/e2e-api'; import { listBootedIOS } from './lib/simctl'; import { formatSummaryLine, readSummary, XcResultError } from './lib/xcresult'; @@ -87,15 +88,19 @@ if (!E2E_EMAIL || !E2E_PASSWORD) { process.exit(1); } const PACKRAT_ENV = process.env.PACKRAT_ENV || 'local'; -const localE2ESessionToken = deriveLocalE2ESessionToken(); -const uiTestEmail = process.env.E2E_TEST_EMAIL ?? E2E_EMAIL; -const uiTestPassword = process.env.E2E_TEST_PASSWORD ?? E2E_PASSWORD; if (!existsSync(SCHEME_PATH)) { console.error(`❌ Scheme not found at ${SCHEME_PATH} — run 'bun swift' first`); process.exit(1); } +const localAPI = await ensureLocalE2EAPI({ packratEnv: PACKRAT_ENV, env: process.env }); +loadEnvFile(resolve(REPO_ROOT, 'packages/api/.dev.vars.e2e'), true); + +const localE2ESessionToken = deriveLocalE2ESessionToken(); +const uiTestEmail = process.env.E2E_TEST_EMAIL ?? E2E_EMAIL; +const uiTestPassword = process.env.E2E_TEST_PASSWORD ?? E2E_PASSWORD; + // ── Inject credentials into scheme ─────────────────────────────────────────── function escapeXml(s: string): string { @@ -110,12 +115,14 @@ function escapeXml(s: string): string { } function deriveLocalE2ESessionToken(): string | undefined { - const dbUrl = process.env.NEON_DATABASE_URL ?? ''; - const secret = process.env.BETTER_AUTH_SECRET; + if (PACKRAT_ENV !== 'local' && PACKRAT_ENV !== 'dev-local') return undefined; + const secret = + process.env.PACKRAT_AUTH_SECRET ?? + process.env.BETTER_AUTH_SECRET ?? + 'e2e-better-auth-secret-at-least-32-chars'; const email = process.env.E2E_TEST_EMAIL?.toLowerCase(); const userId = process.env.E2E_TEST_USER_ID; - if (!(dbUrl.includes('127.0.0.1') || dbUrl.includes('localhost'))) return undefined; - if (!secret || !email || !userId) return undefined; + if (!email || !userId) return undefined; const digest = createHash('sha256').update([secret, email, userId].join(':')).digest('hex'); return `e2e-local.${digest}`; } @@ -278,43 +285,45 @@ function redactSecrets(output: string): string { return redacted; } -const resultStatus = await new Promise((resolve) => { - const child = spawn('xcodebuild', args, { - cwd: SWIFT_DIR, - env: process.env, - }); - - child.stdout.on('data', (chunk) => { - process.stdout.write(redactSecrets(chunk.toString())); - }); - child.stderr.on('data', (chunk) => { - process.stderr.write(redactSecrets(chunk.toString())); +let exitStatus = 1; +try { + const resultStatus = await new Promise((resolve) => { + const child = spawn('xcodebuild', args, { + cwd: SWIFT_DIR, + env: process.env, + }); + + child.stdout.on('data', (chunk) => { + process.stdout.write(redactSecrets(chunk.toString())); + }); + child.stderr.on('data', (chunk) => { + process.stderr.write(redactSecrets(chunk.toString())); + }); + child.on('close', (code) => resolve(code)); }); - child.on('close', (code) => resolve(code)); -}); - -const result = { - status: resultStatus, -}; + exitStatus = resultStatus ?? 1; -// xcodebuild test exits non-zero on test failure but the result bundle is still valid; -// always try to summarize, then propagate the original exit code. -try { - const summary = readSummary(resultBundle); - console.log(''); - console.log(formatSummaryLine(summary)); - if (summary.failingTests.length > 0) { - console.log(' Failing tests:'); - for (const t of summary.failingTests) { - console.log(` • ${t.identifier}`); + // xcodebuild test exits non-zero on test failure but the result bundle is still valid; + // always try to summarize, then propagate the original exit code. + try { + const summary = readSummary(resultBundle); + console.log(''); + console.log(formatSummaryLine(summary)); + if (summary.failingTests.length > 0) { + console.log(' Failing tests:'); + for (const t of summary.failingTests) { + console.log(` • ${t.identifier}`); + } + } + } catch (err) { + if (err instanceof XcResultError) { + console.error(`⚠️ ${err.message}`); + } else { + throw err; } } -} catch (err) { - if (err instanceof XcResultError) { - console.error(`⚠️ ${err.message}`); - } else { - throw err; - } +} finally { + await localAPI.stop(); } -process.exit(result.status ?? 1); +process.exit(exitStatus); diff --git a/apps/swift/scripts/validate-app-store-assets.ts b/apps/swift/scripts/validate-app-store-assets.ts index 1bf92a6ae2..92dbfaf0ec 100644 --- a/apps/swift/scripts/validate-app-store-assets.ts +++ b/apps/swift/scripts/validate-app-store-assets.ts @@ -2,9 +2,12 @@ import { resolve } from 'node:path'; import { validateAppIconSet } from './lib/app-store-assets'; const repoRoot = resolve(import.meta.dir, '../../..'); -const iconSetDir = resolve(repoRoot, 'apps/swift/Resources/Assets.xcassets/AppIcon.appiconset'); +const iconSetDirs = [ + resolve(repoRoot, 'apps/swift/Resources/Assets.xcassets/AppIcon.appiconset'), + resolve(repoRoot, 'apps/swift/Resources/WatchAssets.xcassets/WatchAppIcon.appiconset'), +]; -const issues = validateAppIconSet(iconSetDir); +const issues = iconSetDirs.flatMap((iconSetDir) => validateAppIconSet(iconSetDir)); if (issues.length > 0) { console.error('App Store asset validation failed:'); diff --git a/docs/testing.md b/docs/testing.md index 77a1cbc73b..f2296e8e46 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -221,10 +221,10 @@ Test fixtures must seed users through `userService.createUser`. Do not write new ### Pattern 5 — Swift visual E2E catalog -The native Swift apps have a visual catalog runner that drives `VisualScreenshotTests` on iOS and macOS, exports every named screenshot, validates the required surface matrix, and renders contact sheets for review. +The native Swift apps have a visual catalog runner that drives `VisualScreenshotTests` on iOS, iPad, and macOS, captures the watchOS companion, exports every named screenshot, validates the required surface matrix, and renders contact sheets for review. ```bash -# Full iOS + macOS visual pass. Requires E2E credentials. +# Full iOS + iPad + macOS + watchOS visual pass. Requires E2E credentials. bun swift:screenshots --out artifacts/screenshots # Platform-specific runs while iterating. @@ -245,7 +245,7 @@ The runner writes: Screenshot fixture data must stay behind `PACKRAT_VISUAL_SCREENSHOTS` / `PACKRAT_VISUAL_SAMPLE_DATA` or explicit visual-test launch arguments. Production fallback and offline states should use honest empty or unsynced copy, not realistic dummy packs, trips, or weather that could be mistaken for user data. -CI runs the same catalog through `.github/workflows/swift-visual.yml` on a nightly schedule and by manual dispatch. The workflow uploads the contact sheets and visual `.xcresult` bundles as `swift-visual-screenshots`. macOS visual runs require Automation Mode to be available on the runner; locally, run `automationmodetool enable-automationmode-without-authentication` once before leaving the suite unattended. +CI runs the same catalog through `.github/workflows/swift-visual.yml` on a nightly schedule and by manual dispatch. The default `all` platform run covers iOS, iPad, macOS, and watchOS; `both` remains a legacy alias for the same full spread. The workflow uploads the contact sheets and visual `.xcresult` bundles as `swift-visual-screenshots`. watchOS screenshots are simulator-captured and do not produce an `.xcresult` bundle. macOS visual runs require Automation Mode to be available on the runner; locally, run `automationmodetool enable-automationmode-without-authentication` once before leaving the suite unattended. --- @@ -291,7 +291,7 @@ bun test:scripts # Swift native apps bun swift # regenerate the Xcode project after project.yml or source tree changes bun test:swift:scripts # TypeScript helper tests for simctl/xcresult/script parsing -bun swift:screenshots # visual E2E catalog for iOS + macOS +bun swift:screenshots # visual E2E catalog for iOS + iPad + macOS + watchOS ``` Coverage reports for each workspace: diff --git a/package.json b/package.json index 3a127115f2..a6d76ab0a9 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "test:lint": "bun test scripts/lint/", "test:mcp": "bun run --cwd packages/mcp test", "test:scripts": "vitest run --config scripts/vitest.config.ts", - "test:swift:runner": "bun test apps/swift/scripts/run-e2e.test.ts", + "test:swift:runner": "vitest run --config apps/swift/vitest.config.ts apps/swift/scripts/__tests__/args.test.ts apps/swift/scripts/__tests__/macos-args.test.ts", "test:swift:scripts": "vitest run --config apps/swift/vitest.config.ts", "test:swift:unit": "bun run apps/swift/scripts/run-e2e.ts unit", "trails": "bun run --cwd apps/trails dev", From eaeffcf915dc4579348c4a3bd5b340227d276ced Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:27:53 -0600 Subject: [PATCH 12/81] =?UTF-8?q?=F0=9F=A7=AA=20test:=20require=20real=20a?= =?UTF-8?q?uth=20for=20deployed=20Swift=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-visual.yml | 20 ++ .../Sources/PackRat/Network/AuthManager.swift | 9 +- .../Tests/PackRatMacOSUITests/Info.plist | 8 + .../Tests/PackRatUITests/AppUITestCase.swift | 24 +- .../Tests/PackRatUITests/AuthTests.swift | 8 +- apps/swift/Tests/PackRatUITests/Info.plist | 8 + .../VisualScreenshotTests.swift | 66 +++- apps/swift/project.yml | 16 + .../scripts/capture-visual-screenshots.ts | 84 +++++- apps/swift/scripts/run-e2e-macos.ts | 7 +- apps/swift/scripts/run-e2e.ts | 7 +- apps/swift/scripts/watch-sync-smoke.ts | 1 + ...estflight-loading-and-pr-readiness-plan.md | 284 ++++++++++++++++++ 13 files changed, 525 insertions(+), 17 deletions(-) create mode 100644 docs/plans/2026-07-17-001-fix-swift-testflight-loading-and-pr-readiness-plan.md diff --git a/.github/workflows/swift-visual.yml b/.github/workflows/swift-visual.yml index e84ef75355..437f06c0e5 100644 --- a/.github/workflows/swift-visual.yml +++ b/.github/workflows/swift-visual.yml @@ -113,6 +113,26 @@ jobs: esac echo "PACKRAT_SWIFT_FEATURE_FLAG_PROFILE=${{ inputs.feature_flag_profile || 'default' }}" >> "$GITHUB_ENV" + - name: Verify Swift visual deployed prerequisites + if: ${{ (inputs.api_environment || 'local') == 'dev' }} + env: + NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }} + run: | + missing=() + [ -z "${NEON_DATABASE_URL:-}" ] && missing+=("NEON_DEV_DATABASE_URL") + if [ ${#missing[@]} -gt 0 ]; then + echo "::error::Required Swift visual dev secrets missing: ${missing[*]}" + exit 1 + fi + + - name: Seed dev E2E test user + if: ${{ (inputs.api_environment || 'local') == 'dev' }} + run: bun run --filter @packrat/api db:seed:e2e-user + env: + NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }} + E2E_TEST_EMAIL: ${{ secrets.E2E_TEST_EMAIL }} + E2E_TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }} + - name: Generate Xcode project run: bun swift diff --git a/apps/swift/Sources/PackRat/Network/AuthManager.swift b/apps/swift/Sources/PackRat/Network/AuthManager.swift index ccc985fbe3..337da9b0b6 100644 --- a/apps/swift/Sources/PackRat/Network/AuthManager.swift +++ b/apps/swift/Sources/PackRat/Network/AuthManager.swift @@ -25,7 +25,8 @@ final class AuthManager { UserDefaults.standard.removeObject(forKey: "current_user") UserDefaults.standard.removeObject(forKey: skippedLoginKey) } - if ProcessInfo.processInfo.arguments.contains("--seed-e2e-auth") { + if ProcessInfo.processInfo.arguments.contains("--seed-e2e-auth"), + Self.e2eLoginSeedAllowed { seedE2EAuthenticatedUser() return } @@ -344,6 +345,7 @@ final class AuthManager { private func seedE2ELoginIfAllowed(email: String, password: String) -> Bool { let environment = ProcessInfo.processInfo.environment guard ProcessInfo.processInfo.arguments.contains("--allow-e2e-login-seed"), + Self.e2eLoginSeedAllowed, let expectedEmail = environment["PACKRAT_E2E_EMAIL"], let expectedPassword = environment["PACKRAT_E2E_PASSWORD"], email.caseInsensitiveCompare(expectedEmail) == .orderedSame, @@ -355,6 +357,11 @@ final class AuthManager { seedE2EAuthenticatedUser() return true } + + private static var e2eLoginSeedAllowed: Bool { + let value = ProcessInfo.processInfo.environment["PACKRAT_E2E_ALLOW_LOGIN_SEED"] ?? "" + return value == "1" || value.caseInsensitiveCompare("true") == .orderedSame + } } enum SocialProvider: String { diff --git a/apps/swift/Tests/PackRatMacOSUITests/Info.plist b/apps/swift/Tests/PackRatMacOSUITests/Info.plist index 7482678a7f..c75250e46d 100644 --- a/apps/swift/Tests/PackRatMacOSUITests/Info.plist +++ b/apps/swift/Tests/PackRatMacOSUITests/Info.plist @@ -18,6 +18,8 @@ 1.0 CFBundleVersion 1 + E2E_API_BASE_URL + $(E2E_API_BASE_URL) PACKRAT_E2E_EMAIL $(PACKRAT_E2E_EMAIL) PACKRAT_E2E_PASSWORD @@ -26,7 +28,13 @@ $(PACKRAT_E2E_SESSION_TOKEN) PACKRAT_E2E_USER_ID $(PACKRAT_E2E_USER_ID) + PACKRAT_E2E_ALLOW_LOGIN_SEED + $(PACKRAT_E2E_ALLOW_LOGIN_SEED) PACKRAT_SCREENSHOT_DIR $(PACKRAT_SCREENSHOT_DIR) + PACKRAT_VISUAL_AUTH_MODE + $(PACKRAT_VISUAL_AUTH_MODE) + PACKRAT_VISUAL_PLATFORM + $(PACKRAT_VISUAL_PLATFORM) diff --git a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift index ab67c9e3ef..a0baf954e2 100644 --- a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift +++ b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift @@ -36,10 +36,13 @@ class AppUITestCase: XCTestCase { // bearer token accepted by the worker, avoiding brittle UI sign-in // while still exercising authenticated API routes. app.launchArguments.append("--reset-auth") - // Feature suites are not login tests. Let the known E2E credentials - // seed app auth after the form submit, while AuthTests keeps covering - // the visible auth flows with its own setup. - app.launchArguments.append("--allow-e2e-login-seed") + // Feature suites are not login tests. Local API runs can opt into + // deterministic seeded auth; deployed API runs must use real login so + // TestFlight-style failures are visible in E2E and screenshots. + if e2eLoginSeedAllowed { + app.launchArguments.append("--allow-e2e-login-seed") + app.launchEnvironment["PACKRAT_E2E_ALLOW_LOGIN_SEED"] = "1" + } app.launchArguments.append(contentsOf: additionalLaunchArguments) if let apiBaseURL = ProcessInfo.processInfo.environment["E2E_API_BASE_URL"], !apiBaseURL.isEmpty { app.launchEnvironment["E2E_API_BASE_URL"] = apiBaseURL @@ -50,7 +53,7 @@ class AppUITestCase: XCTestCase { ProcessInfo.processInfo.environment["PACKRAT_E2E_EMAIL"] ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_EMAIL") as? String) ?? "" - if !seededAuthEmail.isEmpty { + if e2eLoginSeedAllowed && !seededAuthEmail.isEmpty { let runnerEnvironment = ProcessInfo.processInfo.environment app.launchArguments.append("--seed-e2e-auth") app.launchEnvironment["PACKRAT_E2E_SESSION_TOKEN"] = runnerEnvironment["PACKRAT_E2E_SESSION_TOKEN"] @@ -72,6 +75,14 @@ class AppUITestCase: XCTestCase { // MARK: - Login helper + var e2eLoginSeedAllowed: Bool { + let bundle = Bundle(for: AppUITestCase.self) + let value = ProcessInfo.processInfo.environment["PACKRAT_E2E_ALLOW_LOGIN_SEED"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_ALLOW_LOGIN_SEED") as? String) + ?? "" + return value == "1" || value.caseInsensitiveCompare("true") == .orderedSame + } + /// Best-effort cross-platform "is the user logged in?" detector. /// /// iOS: tab bar is the unmistakable signal. macOS uses a NavigationSplitView @@ -144,6 +155,9 @@ class AppUITestCase: XCTestCase { app.launchEnvironment["PACKRAT_E2E_USER_ID"] = runnerEnvironment["PACKRAT_E2E_USER_ID"] ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_USER_ID") as? String) ?? "" + if e2eLoginSeedAllowed { + app.launchEnvironment["PACKRAT_E2E_ALLOW_LOGIN_SEED"] = "1" + } } func submitLoginForm() { diff --git a/apps/swift/Tests/PackRatUITests/AuthTests.swift b/apps/swift/Tests/PackRatUITests/AuthTests.swift index d66b170190..52045c2f7c 100644 --- a/apps/swift/Tests/PackRatUITests/AuthTests.swift +++ b/apps/swift/Tests/PackRatUITests/AuthTests.swift @@ -8,7 +8,10 @@ final class AuthTests: AppUITestCase { app.launchArguments.append("--use-userdefaults-auth") // Force logged-out state so the login screen is reachable. app.launchArguments.append("--reset-auth") - app.launchArguments.append("--allow-e2e-login-seed") + if e2eLoginSeedAllowed { + app.launchArguments.append("--allow-e2e-login-seed") + app.launchEnvironment["PACKRAT_E2E_ALLOW_LOGIN_SEED"] = "1" + } if let apiBaseURL = ProcessInfo.processInfo.environment["E2E_API_BASE_URL"], !apiBaseURL.isEmpty { app.launchEnvironment["E2E_API_BASE_URL"] = apiBaseURL } @@ -249,6 +252,9 @@ final class AuthTests: AppUITestCase { (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_SESSION_TOKEN") as? String) ?? "" app.launchEnvironment["PACKRAT_E2E_USER_ID"] = (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_USER_ID") as? String) ?? "" + if e2eLoginSeedAllowed { + app.launchEnvironment["PACKRAT_E2E_ALLOW_LOGIN_SEED"] = "1" + } } #if os(iOS) diff --git a/apps/swift/Tests/PackRatUITests/Info.plist b/apps/swift/Tests/PackRatUITests/Info.plist index 7482678a7f..c75250e46d 100644 --- a/apps/swift/Tests/PackRatUITests/Info.plist +++ b/apps/swift/Tests/PackRatUITests/Info.plist @@ -18,6 +18,8 @@ 1.0 CFBundleVersion 1 + E2E_API_BASE_URL + $(E2E_API_BASE_URL) PACKRAT_E2E_EMAIL $(PACKRAT_E2E_EMAIL) PACKRAT_E2E_PASSWORD @@ -26,7 +28,13 @@ $(PACKRAT_E2E_SESSION_TOKEN) PACKRAT_E2E_USER_ID $(PACKRAT_E2E_USER_ID) + PACKRAT_E2E_ALLOW_LOGIN_SEED + $(PACKRAT_E2E_ALLOW_LOGIN_SEED) PACKRAT_SCREENSHOT_DIR $(PACKRAT_SCREENSHOT_DIR) + PACKRAT_VISUAL_AUTH_MODE + $(PACKRAT_VISUAL_AUTH_MODE) + PACKRAT_VISUAL_PLATFORM + $(PACKRAT_VISUAL_PLATFORM) diff --git a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift index cc51031cd7..acfff8c2f9 100644 --- a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift +++ b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift @@ -43,10 +43,13 @@ final class VisualScreenshotTests: XCTestCase { private var isPadVisualRun: Bool { #if os(iOS) - ProcessInfo.processInfo.environment["PACKRAT_VISUAL_PLATFORM"] == "ipad" + let bundle = Bundle(for: VisualScreenshotTests.self) + let platform = ProcessInfo.processInfo.environment["PACKRAT_VISUAL_PLATFORM"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_VISUAL_PLATFORM") as? String) + return platform == "ipad" || screenshotDirectory?.path.contains("ipad") == true #else - false + return false #endif } @@ -1284,6 +1287,11 @@ final class VisualScreenshotTests: XCTestCase { ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_USER_ID") as? String) ?? "00000000-0000-4000-8000-000000000001" + if usesRealVisualLogin && !sampleData && !forceOffline { + launchRealAuthenticated(email: email) + return + } + app.terminate() app = XCUIApplication() app.launchArguments = [ @@ -1297,6 +1305,7 @@ final class VisualScreenshotTests: XCTestCase { app.launchEnvironment["PACKRAT_E2E_EMAIL"] = email app.launchEnvironment["PACKRAT_E2E_USER_ID"] = userId app.launchEnvironment["PACKRAT_E2E_SESSION_TOKEN"] = visualE2ESessionToken(email: email, userId: userId) + app.launchEnvironment["PACKRAT_E2E_ALLOW_LOGIN_SEED"] = "1" if sampleData { app.launchArguments.append("--visual-sample-data") app.launchEnvironment["PACKRAT_VISUAL_SAMPLE_DATA"] = "1" @@ -1313,6 +1322,54 @@ final class VisualScreenshotTests: XCTestCase { XCTAssertTrue(waitForAuthenticatedShell(), "Authenticated visual shell must launch from seeded E2E state") } + private var usesRealVisualLogin: Bool { + let bundle = Bundle(for: VisualScreenshotTests.self) + return ProcessInfo.processInfo.environment["PACKRAT_VISUAL_AUTH_MODE"] == "real" + || (bundle.object(forInfoDictionaryKey: "PACKRAT_VISUAL_AUTH_MODE") as? String) == "real" + } + + private func launchRealAuthenticated(email: String) { + let bundle = Bundle(for: VisualScreenshotTests.self) + let password = (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_PASSWORD") as? String) ?? "" + XCTAssertFalse(password.isEmpty, "PACKRAT_E2E_PASSWORD is required for real visual login") + + app.terminate() + app = XCUIApplication() + app.launchArguments = [ + "--disable-animations", + "--use-userdefaults-auth", + "--reset-auth", + ] + app.launchEnvironment["PACKRAT_VISUAL_SCREENSHOTS"] = "1" + app.launchEnvironment["E2E_API_BASE_URL"] = visualE2EAPIBaseURL + app.launch() + #if os(macOS) + app.activate() + dismissSystemInterruptions() + #endif + + let signIn = app.buttons["auth_sign_in"] + if signIn.waitForExistence(timeout: 10) { + signIn.tap() + } + + let emailField = app.textFields["login_email"] + XCTAssertTrue(emailField.waitForExistence(timeout: 10), "Login screen must appear for real visual login") + emailField.tap() + emailField.typeText(email) + + let passwordField = app.secureTextFields["login_password"] + passwordField.tap() + passwordField.typeText(password) + + #if os(macOS) + app.typeKey(XCUIKeyboardKey.escape.rawValue, modifierFlags: []) + #endif + app.buttons["login_submit"].tap() + + XCTAssertTrue(waitForAuthenticatedShell(), "Authenticated visual shell must launch from real login") + } + private func waitForAuthenticatedShell() -> Bool { #if os(iOS) if isPadVisualRun { @@ -1346,7 +1403,10 @@ final class VisualScreenshotTests: XCTestCase { } private var visualE2EAPIBaseURL: String { - ProcessInfo.processInfo.environment["E2E_API_BASE_URL"] ?? "http://localhost:8787" + let bundle = Bundle(for: VisualScreenshotTests.self) + return ProcessInfo.processInfo.environment["E2E_API_BASE_URL"] + ?? (bundle.object(forInfoDictionaryKey: "E2E_API_BASE_URL") as? String) + ?? "http://localhost:8787" } private func visualE2ESessionToken(email: String, userId: String) -> String { diff --git a/apps/swift/project.yml b/apps/swift/project.yml index fd055c1fae..94ea799fe4 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -273,19 +273,27 @@ targets: CFBundlePackageType: BNDL CFBundleShortVersionString: "1.0" CFBundleVersion: "1" + E2E_API_BASE_URL: $(E2E_API_BASE_URL) PACKRAT_E2E_EMAIL: $(PACKRAT_E2E_EMAIL) PACKRAT_E2E_PASSWORD: $(PACKRAT_E2E_PASSWORD) PACKRAT_E2E_SESSION_TOKEN: $(PACKRAT_E2E_SESSION_TOKEN) PACKRAT_E2E_USER_ID: $(PACKRAT_E2E_USER_ID) + PACKRAT_E2E_ALLOW_LOGIN_SEED: $(PACKRAT_E2E_ALLOW_LOGIN_SEED) PACKRAT_SCREENSHOT_DIR: $(PACKRAT_SCREENSHOT_DIR) + PACKRAT_VISUAL_AUTH_MODE: $(PACKRAT_VISUAL_AUTH_MODE) + PACKRAT_VISUAL_PLATFORM: $(PACKRAT_VISUAL_PLATFORM) settings: base: SWIFT_VERSION: "5.9" + E2E_API_BASE_URL: "" PACKRAT_E2E_EMAIL: "" PACKRAT_E2E_PASSWORD: "" PACKRAT_E2E_SESSION_TOKEN: "" PACKRAT_E2E_USER_ID: "" + PACKRAT_E2E_ALLOW_LOGIN_SEED: "" PACKRAT_SCREENSHOT_DIR: "" + PACKRAT_VISUAL_AUTH_MODE: "" + PACKRAT_VISUAL_PLATFORM: "" PackRatMacOSTests: type: bundle.unit-test @@ -318,19 +326,27 @@ targets: CFBundlePackageType: BNDL CFBundleShortVersionString: "1.0" CFBundleVersion: "1" + E2E_API_BASE_URL: $(E2E_API_BASE_URL) PACKRAT_E2E_EMAIL: $(PACKRAT_E2E_EMAIL) PACKRAT_E2E_PASSWORD: $(PACKRAT_E2E_PASSWORD) PACKRAT_E2E_SESSION_TOKEN: $(PACKRAT_E2E_SESSION_TOKEN) PACKRAT_E2E_USER_ID: $(PACKRAT_E2E_USER_ID) + PACKRAT_E2E_ALLOW_LOGIN_SEED: $(PACKRAT_E2E_ALLOW_LOGIN_SEED) PACKRAT_SCREENSHOT_DIR: $(PACKRAT_SCREENSHOT_DIR) + PACKRAT_VISUAL_AUTH_MODE: $(PACKRAT_VISUAL_AUTH_MODE) + PACKRAT_VISUAL_PLATFORM: $(PACKRAT_VISUAL_PLATFORM) settings: base: SWIFT_VERSION: "5.9" + E2E_API_BASE_URL: "" PACKRAT_E2E_EMAIL: "" PACKRAT_E2E_PASSWORD: "" PACKRAT_E2E_SESSION_TOKEN: "" PACKRAT_E2E_USER_ID: "" + PACKRAT_E2E_ALLOW_LOGIN_SEED: "" PACKRAT_SCREENSHOT_DIR: "" + PACKRAT_VISUAL_AUTH_MODE: "" + PACKRAT_VISUAL_PLATFORM: "" schemes: PackRat-iOS: diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index 6ebdbce16f..5aa9159727 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -349,6 +349,7 @@ function redactSecrets(output: string): string { if (!secret) continue; redacted = redacted.split(secret).join('[REDACTED]'); } + redacted = redacted.replace(/e2e-local\.[a-f0-9]{64}/gi, '[REDACTED_E2E_TOKEN]'); redacted = redacted.replace(EMAIL_RE, '[REDACTED_EMAIL]'); redacted = redacted.replace(SECRET_BUILD_SETTING_RE, (match) => { const equalsIndex = match.indexOf('='); @@ -918,14 +919,29 @@ function allocateResultBundle(platform: Platform): string { return path; } -function runXcodeVisualTest(platform: Platform, screenshotDir: string): Promise { +async function runXcodeVisualTest( + platform: Platform, + screenshotDir: string, +): Promise { if (platform === 'watch') return runWatchVisualCapture(screenshotDir); const resultBundle = allocateResultBundle(platform); const writableScreenshotDir = allocateWritableScreenshotDir(platform); - const credentials = e2eBuildSettings(); + const packratEnv = Bun.env.PACKRAT_ENV ?? nodeEnv.PACKRAT_ENV ?? 'local'; + const authMode = visualAuthMode(packratEnv); + const apiBaseURL = Bun.env.E2E_API_BASE_URL ?? ''; + const credentials = e2eBuildSettings(packratEnv); + const visualBuildSettings = [ + ...(apiBaseURL ? [`E2E_API_BASE_URL=${apiBaseURL}`] : []), + `PACKRAT_ENV=${packratEnv}`, + `PACKRAT_VISUAL_AUTH_MODE=${authMode}`, + `PACKRAT_VISUAL_PLATFORM=${platform}`, + ]; injectVisualSchemeEnvironment(platform, { + E2E_API_BASE_URL: apiBaseURL, + PACKRAT_ENV: packratEnv, PACKRAT_SCREENSHOT_DIR: writableScreenshotDir, + PACKRAT_VISUAL_AUTH_MODE: authMode, PACKRAT_VISUAL_PLATFORM: platform, ...buildSettingsToEnv(credentials), }); @@ -944,6 +960,7 @@ function runXcodeVisualTest(platform: Platform, screenshotDir: string): Promise< '-destination', pickIOSDestination(platform), '-only-testing:PackRatUITests/VisualScreenshotTests', + ...visualBuildSettings, ...credentials, ] : [ @@ -958,6 +975,7 @@ function runXcodeVisualTest(platform: Platform, screenshotDir: string): Promise< 'CODE_SIGN_IDENTITY=-', 'CODE_SIGNING_ALLOWED=YES', 'CODE_SIGNING_REQUIRED=NO', + ...visualBuildSettings, ...credentials, ]; @@ -966,6 +984,8 @@ function runXcodeVisualTest(platform: Platform, screenshotDir: string): Promise< console.log(`→ XCTest write dir: ${writableScreenshotDir}`); console.log(`→ Result bundle: ${resultBundle}`); + await assertDeployedVisualAuthReady({ packratEnv, apiBaseURL, authMode }); + if (platform === 'macos') { assertAutomationModeAvailable(); clearMacNotificationBanners(); @@ -978,7 +998,9 @@ function runXcodeVisualTest(platform: Platform, screenshotDir: string): Promise< cwd: SWIFT_DIR, env: { ...Bun.env, - PACKRAT_ENV: Bun.env.PACKRAT_ENV ?? nodeEnv.PACKRAT_ENV ?? 'local', + E2E_API_BASE_URL: apiBaseURL, + PACKRAT_ENV: packratEnv, + PACKRAT_VISUAL_AUTH_MODE: authMode, PACKRAT_SCREENSHOT_DIR: writableScreenshotDir, PACKRAT_VISUAL_PLATFORM: platform, }, @@ -1448,7 +1470,13 @@ const AttachmentManifestEntrySchema = z .passthrough(); const parseAttachmentManifest = fromZod(z.array(AttachmentManifestEntrySchema)); -function e2eBuildSettings(): string[] { +function visualAuthMode(packratEnv: string): 'seeded' | 'real' { + const explicit = Bun.env.PACKRAT_VISUAL_AUTH_MODE; + if (explicit === 'seeded' || explicit === 'real') return explicit; + return packratEnv === 'local' || packratEnv === 'dev-local' ? 'seeded' : 'real'; +} + +function e2eBuildSettings(packratEnv: string): string[] { const apiE2EEnv = readSimpleEnvFile(API_E2E_ENV_PATH); const email = Bun.env.E2E_TEST_EMAIL ?? Bun.env.E2E_EMAIL ?? apiE2EEnv.E2E_TEST_EMAIL; const password = Bun.env.E2E_TEST_PASSWORD ?? Bun.env.E2E_PASSWORD ?? apiE2EEnv.E2E_TEST_PASSWORD; @@ -1460,9 +1488,10 @@ function e2eBuildSettings(): string[] { } const userId = Bun.env.E2E_TEST_USER_ID ?? apiE2EEnv.E2E_TEST_USER_ID; + const shouldSynthesizeLocalToken = packratEnv === 'local' || packratEnv === 'dev-local'; const sessionToken = Bun.env.PACKRAT_E2E_SESSION_TOKEN ?? - (userId + (shouldSynthesizeLocalToken && userId ? localE2ESessionToken({ secret: Bun.env.BETTER_AUTH_SECRET ?? @@ -1476,11 +1505,56 @@ function e2eBuildSettings(): string[] { return [ `PACKRAT_E2E_EMAIL=${email}`, `PACKRAT_E2E_PASSWORD=${password}`, + `PACKRAT_E2E_ALLOW_LOGIN_SEED=${shouldSynthesizeLocalToken ? '1' : '0'}`, ...(userId ? [`PACKRAT_E2E_USER_ID=${userId}`] : []), ...(sessionToken ? [`PACKRAT_E2E_SESSION_TOKEN=${sessionToken}`] : []), ]; } +async function assertDeployedVisualAuthReady(input: { + packratEnv: string; + apiBaseURL: string; + authMode: 'seeded' | 'real'; +}): Promise { + const { packratEnv, apiBaseURL, authMode } = input; + if (authMode !== 'real') return; + + const baseURL = + apiBaseURL || + (packratEnv === 'dev' + ? 'https://packrat-api-dev.orange-frost-d665.workers.dev' + : packratEnv === 'production' + ? 'https://packrat-api.orange-frost-d665.workers.dev' + : ''); + if (!baseURL) return; + + const email = Bun.env.E2E_TEST_EMAIL ?? Bun.env.E2E_EMAIL ?? apiE2EEnv.E2E_TEST_EMAIL; + const password = Bun.env.E2E_TEST_PASSWORD ?? Bun.env.E2E_PASSWORD ?? apiE2EEnv.E2E_TEST_PASSWORD; + if (!email || !password) { + throw new Error( + `PACKRAT_VISUAL_AUTH_MODE=real for PACKRAT_ENV=${packratEnv}, but E2E credentials are missing.`, + ); + } + + const response = await fetch(`${baseURL}/api/auth/sign-in/email`, { + method: 'POST', + headers: { accept: 'application/json', 'content-type': 'application/json' }, + body: JSON.stringify({ email, password }), + }); + if (!response.ok) { + throw new Error( + [ + `Swift visual real-auth preflight failed for PACKRAT_ENV=${packratEnv}: ${response.status}.`, + packratEnv === 'dev' + ? 'Seed the dev E2E user before capturing deployed-dev screenshots.' + : 'Use a valid production QA account before capturing production/TestFlight screenshots.', + 'The visual runner no longer falls back to seeded auth for deployed APIs.', + ].join(' '), + ); + } + console.log(`✓ Verified deployed ${packratEnv} E2E auth before visual capture`); +} + function localE2ESessionToken(input: { secret: string; email: string; userId: string }): string { const material = `${input.secret}:${input.email.toLowerCase()}:${input.userId}`; return `e2e-local.${createHash('sha256').update(material).digest('hex')}`; diff --git a/apps/swift/scripts/run-e2e-macos.ts b/apps/swift/scripts/run-e2e-macos.ts index 86c24feb5f..98d16b8947 100644 --- a/apps/swift/scripts/run-e2e-macos.ts +++ b/apps/swift/scripts/run-e2e-macos.ts @@ -98,6 +98,7 @@ const localAPI = await ensureLocalE2EAPI({ packratEnv: PACKRAT_ENV, env: process loadEnvFile(resolve(REPO_ROOT, 'packages/api/.dev.vars.e2e'), true); const localE2ESessionToken = deriveLocalE2ESessionToken(); +const allowLoginSeed = PACKRAT_ENV === 'local' || PACKRAT_ENV === 'dev-local'; const uiTestEmail = process.env.E2E_TEST_EMAIL ?? E2E_EMAIL; const uiTestPassword = process.env.E2E_TEST_PASSWORD ?? E2E_PASSWORD; @@ -150,6 +151,7 @@ type SchemeEnv = { password: string; sessionToken?: string; userId?: string; + allowLoginSeed: boolean; }; function environmentVariableXml(key: string, value: string): string { @@ -162,7 +164,7 @@ function environmentVariableXml(key: string, value: string): string { ].join('\n'); } -function injectScheme({ email, password, sessionToken, userId }: SchemeEnv): void { +function injectScheme({ email, password, sessionToken, userId, allowLoginSeed }: SchemeEnv): void { let content = readFileSync(SCHEME_PATH, 'utf8'); content = removeEnvironmentVariablesBlock(content); content = content.replace( @@ -174,6 +176,7 @@ function injectScheme({ email, password, sessionToken, userId }: SchemeEnv): voi environmentVariableXml('E2E_PASSWORD', password), environmentVariableXml('PACKRAT_E2E_EMAIL', uiTestEmail), environmentVariableXml('PACKRAT_E2E_PASSWORD', uiTestPassword), + environmentVariableXml('PACKRAT_E2E_ALLOW_LOGIN_SEED', allowLoginSeed ? '1' : '0'), ]; if (sessionToken) variables.push(environmentVariableXml('PACKRAT_E2E_SESSION_TOKEN', sessionToken)); @@ -241,6 +244,7 @@ injectScheme({ password: E2E_PASSWORD, sessionToken: localE2ESessionToken, userId: process.env.E2E_TEST_USER_ID, + allowLoginSeed, }); console.log('✓ Injected E2E credentials into PackRat-macOS scheme'); assertAutomationModeAvailable(); @@ -269,6 +273,7 @@ const args = [ `PACKRAT_E2E_PASSWORD=${uiTestPassword}`, `PACKRAT_E2E_SESSION_TOKEN=${localE2ESessionToken ?? ''}`, `PACKRAT_E2E_USER_ID=${process.env.E2E_TEST_USER_ID ?? ''}`, + `PACKRAT_E2E_ALLOW_LOGIN_SEED=${allowLoginSeed ? '1' : '0'}`, `PACKRAT_ENV=${PACKRAT_ENV}`, ]; diff --git a/apps/swift/scripts/run-e2e.ts b/apps/swift/scripts/run-e2e.ts index 5c03ea5db4..cd014bb6e6 100644 --- a/apps/swift/scripts/run-e2e.ts +++ b/apps/swift/scripts/run-e2e.ts @@ -98,6 +98,7 @@ const localAPI = await ensureLocalE2EAPI({ packratEnv: PACKRAT_ENV, env: process loadEnvFile(resolve(REPO_ROOT, 'packages/api/.dev.vars.e2e'), true); const localE2ESessionToken = deriveLocalE2ESessionToken(); +const allowLoginSeed = PACKRAT_ENV === 'local' || PACKRAT_ENV === 'dev-local'; const uiTestEmail = process.env.E2E_TEST_EMAIL ?? E2E_EMAIL; const uiTestPassword = process.env.E2E_TEST_PASSWORD ?? E2E_PASSWORD; @@ -132,6 +133,7 @@ type SchemeEnv = { password: string; sessionToken?: string; userId?: string; + allowLoginSeed: boolean; }; function environmentVariableXml(key: string, value: string): string { @@ -144,7 +146,7 @@ function environmentVariableXml(key: string, value: string): string { ].join('\n'); } -function injectScheme({ email, password, sessionToken, userId }: SchemeEnv): void { +function injectScheme({ email, password, sessionToken, userId, allowLoginSeed }: SchemeEnv): void { let content = readFileSync(SCHEME_PATH, 'utf8'); // Strip any prior EnvironmentVariables block (idempotent re-runs). @@ -161,6 +163,7 @@ function injectScheme({ email, password, sessionToken, userId }: SchemeEnv): voi environmentVariableXml('E2E_PASSWORD', password), environmentVariableXml('PACKRAT_E2E_EMAIL', uiTestEmail), environmentVariableXml('PACKRAT_E2E_PASSWORD', uiTestPassword), + environmentVariableXml('PACKRAT_E2E_ALLOW_LOGIN_SEED', allowLoginSeed ? '1' : '0'), ]; if (sessionToken) variables.push(environmentVariableXml('PACKRAT_E2E_SESSION_TOKEN', sessionToken)); @@ -233,6 +236,7 @@ injectScheme({ password: E2E_PASSWORD, sessionToken: localE2ESessionToken, userId: process.env.E2E_TEST_USER_ID, + allowLoginSeed, }); console.log('✓ Injected E2E credentials into scheme'); @@ -263,6 +267,7 @@ const args = [ `PACKRAT_E2E_PASSWORD=${uiTestPassword}`, `PACKRAT_E2E_SESSION_TOKEN=${localE2ESessionToken ?? ''}`, `PACKRAT_E2E_USER_ID=${process.env.E2E_TEST_USER_ID ?? ''}`, + `PACKRAT_E2E_ALLOW_LOGIN_SEED=${allowLoginSeed ? '1' : '0'}`, `PACKRAT_ENV=${PACKRAT_ENV}`, ]; diff --git a/apps/swift/scripts/watch-sync-smoke.ts b/apps/swift/scripts/watch-sync-smoke.ts index 42449f583b..ee93080e9c 100644 --- a/apps/swift/scripts/watch-sync-smoke.ts +++ b/apps/swift/scripts/watch-sync-smoke.ts @@ -298,6 +298,7 @@ async function main() { { env: { SIMCTL_CHILD_PACKRAT_VISUAL_SAMPLE_DATA: '1', + SIMCTL_CHILD_PACKRAT_E2E_ALLOW_LOGIN_SEED: '1', SIMCTL_CHILD_PACKRAT_E2E_EMAIL: 'e2e@packrat.test', SIMCTL_CHILD_PACKRAT_E2E_USER_ID: '00000000-0000-4000-8000-000000000001', SIMCTL_CHILD_PACKRAT_E2E_ROLE: 'ADMIN', diff --git a/docs/plans/2026-07-17-001-fix-swift-testflight-loading-and-pr-readiness-plan.md b/docs/plans/2026-07-17-001-fix-swift-testflight-loading-and-pr-readiness-plan.md new file mode 100644 index 0000000000..6b2c27bedb --- /dev/null +++ b/docs/plans/2026-07-17-001-fix-swift-testflight-loading-and-pr-readiness-plan.md @@ -0,0 +1,284 @@ +--- +artifact_contract: ce-unified-plan/v1 +artifact_readiness: implementation-ready +execution: code +title: "fix: Swift TestFlight loading and PR readiness" +type: fix +status: active +created: 2026-07-17 +target_branch: codex/swift-beta-tester-readiness +base_branch: development +product_contract_source: ce-plan-bootstrap +--- + +# fix: Swift TestFlight loading and PR readiness + +## Goal Capsule + +Make the Swift beta PR mergeable and prove the TestFlight-like build path is not hiding loading, auth, feature-flag, or production API failures behind local-only E2E fixtures. + +## Product Contract + +### Problem Frame + +The Swift beta branch has substantial iOS, iPad, macOS, and watchOS polish and E2E work, but the user still observes TestFlight loading issues and worries that local tests are not exercising the deployed release path. The active PR is open but GitHub marks it conflicting against `development`, so the branch cannot be safely merged until conflict resolution and production-like validation are complete. + +### Requirements + +- **R1**: Resolve the PR conflicts against `development` without dropping the Swift beta, visual screenshot, or E2E coverage work. +- **R2**: Reproduce TestFlight-like loading behavior using Release/production API configuration, not only local deterministic E2E fixtures. +- **R3**: Exercise Swift feature flags in at least the canonical default profile and a temporary all-on profile so disabled feature surfaces do not silently rot. +- **R4**: Keep tests honest: do not hide real app/backend bugs by weakening assertions, forcing success states, or seeding user-confusing dummy production data. +- **R5**: Preserve guest and authenticated coverage across iOS, iPad, macOS, and relevant watchOS surfaces where the local tooling supports it. +- **R6**: Ship any fixes as small gitmoji commits to the existing PR branch and watch CI to a decided state. +- **R7**: If unrelated repo-wide checks fail, separate them from the Swift beta readiness path and only hand off to another worktree/subagent when the scope is cleanly separable. + +### Acceptance Examples + +- **AE1**: Running the Swift E2E workflow manually with `api_environment=production` and `feature_flag_profile=default` reaches authenticated app screens with real production test credentials or fails with a specific app/API defect. +- **AE2**: Running the Swift visual workflow with `api_environment=production` produces iOS, iPad, macOS, and watchOS contact sheets without indefinite loading states on authenticated screens. +- **AE3**: Running local default and all-on feature flag config generation produces expected generated Swift flags and does not leave the working tree dirty after returning to default. +- **AE4**: GitHub PR #2627 no longer reports `CONFLICTING`. + +## Scope Boundaries + +In scope: +- Conflict resolution for the active Swift beta readiness PR. +- Swift app/API fixes needed to make production-like TestFlight loading, auth, and screenshots work. +- Manual workflow hardening where the deployed validation path needs clearer inputs or artifacts. +- Focused tests for any behavior changes. + +Out of scope: +- Expo E2E fixes unrelated to the Swift beta rollout. +- Production database dummy seeding that would confuse testers. +- Apple Developer/App Store Connect account changes unless required to inspect TestFlight metadata. +- Large unrelated repo-wide lint cleanup unless it directly blocks the Swift PR. + +## Settled Decisions + +- **KTD-session-settled-1**: Validate Swift iOS and macOS first; do not get caught up in Expo web/E2E right now. + - Provenance: user-directed. + - Rejected alternative: broaden the current work into Expo web or GitHub Expo E2E repair. + - Reason: the beta tester risk is the Swift app and TestFlight path. +- **KTD-session-settled-2**: Keep the app local-first and guest-capable, while testing authenticated mode too. + - Provenance: user-approved. + - Rejected alternative: gate all useful screens behind auth or test only guest mode. + - Reason: testers need basic functionality without sign-in, and production issues are often auth-only. +- **KTD-session-settled-3**: Use SwiftUI-native defaults and reusable empty/error/loading patterns. + - Provenance: user-approved. + - Rejected alternative: custom controls and inconsistent ad hoc state views. + - Reason: the desired product feel is Apple-native and maintainable. +- **KTD-session-settled-4**: Use visual contact sheets as a required review artifact. + - Provenance: user-approved. + - Rejected alternative: rely only on textual test output. + - Reason: screenshots revealed error, empty-state, layout, and modal coverage gaps. +- **Report conflicts**: If implementation finds a settled decision is infeasible or harmful, stop for invalidating conflicts; proceed with a documented note for preference-grade conflicts. + +## Current Context + +- PR #2627 exists: `https://github.com/PackRat-AI/PackRat/pull/2627`. +- Local branch is clean and tracks `origin/codex/swift-beta-tester-readiness`. +- GitHub currently reports PR #2627 as `CONFLICTING`. +- CodeQL checks are green at the latest observed state. +- This worktree originally tracked only the PR branch; fetch refs for `origin/development` and `origin/main` must be available before merge/rebase work. +- Release Swift config uses `PACKRAT_ENV = production`. +- Production API base URL is `https://packrat-api.orange-frost-d665.workers.dev`. +- Local Swift E2E and screenshots can use deterministic local E2E API fixtures, which is useful but does not prove TestFlight behavior. + +## Implementation Units + +### U1. Resolve base sync and PR conflicts + +**Goal:** Bring the branch onto current `development` and make PR #2627 mergeable. + +**Requirements:** R1, R6. + +**Files:** +- Potentially any conflicted files reported by merging or rebasing `origin/development`. +- Likely conflict candidates: `.github/workflows/swift-e2e.yml`, `.github/workflows/swift-visual.yml`, `apps/swift/project.yml`, Swift test files, and API E2E fixture routes. + +**Approach:** +- Fetch `origin/development` explicitly because this worktree uses a narrow fetch refspec. +- Use a non-destructive merge or rebase strategy that preserves the PR branch work and does not revert unrelated user changes. +- Inspect each conflict manually and preserve both development-side updates and Swift beta additions when compatible. +- Regenerate any generated Swift config/project artifacts only through repo scripts. + +**Test scenarios:** +- PR conflict state changes from `CONFLICTING` to mergeable after push. +- Local diff still includes the Swift beta screenshot/E2E/watch/app-icon work expected for this PR. +- No conflict markers remain. + +**Verification:** +- `git diff --check`. +- `rg -n '<<<<<<<|=======|>>>>>>>'`. +- `gh pr view 2627 --json mergeable`. + +### U2. Reproduce TestFlight-like loading locally and in workflow + +**Goal:** Determine whether TestFlight loading is a real production API/auth/config bug, a Release build configuration issue, or a workflow coverage gap. + +**Requirements:** R2, R4, R5. + +**Files:** +- `apps/swift/xcconfig/Config-Release.xcconfig` +- `apps/swift/Sources/PackRat/Network/APIClient.swift` +- `apps/swift/Sources/PackRat/Network/AuthManager.swift` +- `apps/swift/Sources/PackRat/PackRatApp.swift` +- `apps/swift/Tests/PackRatUITests/AppUITestCase.swift` +- `apps/swift/Tests/PackRatUITests/AuthTests.swift` +- `apps/swift/scripts/run-e2e.ts` +- `apps/swift/scripts/run-e2e-macos.ts` +- `apps/swift/scripts/capture-visual-screenshots.ts` +- `.github/workflows/swift-e2e.yml` +- `.github/workflows/swift-visual.yml` + +**Approach:** +- Run a production-target smoke path with `PACKRAT_ENV=production` and explicit production `E2E_API_BASE_URL` using existing test credentials when available. +- If credentials are unavailable locally, make the workflow produce a clear missing-secret failure instead of falling back to local fixtures. +- Compare app launch environment, API base URL selection, auth cookie/session handling, and loading/error states between local and production targets. +- Fix any app bug that causes indefinite loading, incorrect connection-needed fallback, or authenticated screens to fail with production-shaped responses. +- Keep production tests read-safe; do not seed production with fake tester-visible records. + +**Test scenarios:** +- Guest launch reaches Home without indefinite loading when production API is reachable. +- Authenticated login reaches the authenticated Home/profile/packs surfaces or fails with a specific auth assertion. +- Protected route `401` maps to sign-in-required or logged-out state, not generic connection-needed. +- API/network failures map to reusable centered offline/error states. +- Production API base URL is visible in logs/artifacts without leaking secrets. + +**Verification:** +- Local smoke command with `PACKRAT_ENV=production` where credentials exist. +- Manual GitHub workflow run with `api_environment=production`. +- Screenshot catalog review for loading/error states. + +### U3. Feature flag matrix hardening + +**Goal:** Exercise both default Swift beta flags and an all-on exploratory profile without committing temporary generated flag changes. + +**Requirements:** R3, R4, R5. + +**Files:** +- `apps/swift/scripts/generate-swift-config.ts` +- `apps/swift/Sources/PackRat/Config/AppFeatureFlags.swift` +- `apps/swift/Tests/PackRatUITests/UITestFeatureFlags.swift` +- `apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift` +- `.github/workflows/swift-e2e.yml` +- `.github/workflows/swift-visual.yml` + +**Approach:** +- Validate generated flags under `default`, `all-on`, and restored `default`. +- Run a focused local all-on screenshot/E2E subset if runtime permits. +- If all-on exposes intentionally incomplete features, either fix the feature enough for the beta or keep it disabled and ensure tests explicitly skip it by generated flag. + +**Test scenarios:** +- Default profile reflects canonical app config. +- All-on profile enables every generated Swift flag. +- All-off profile does not crash launch/navigation. +- Generated files return to default before commit unless the product config itself changes. + +**Verification:** +- `bun run swift:config`. +- `PACKRAT_SWIFT_FEATURE_FLAG_PROFILE=all-on bun run swift:config`. +- `PACKRAT_SWIFT_FEATURE_FLAG_PROFILE=all-off bun run swift:config`. +- Restore default and verify `git diff` is expected. + +### U4. Screenshot/contact-sheet audit for loading and missing states + +**Goal:** Produce visual evidence that all major Swift states render correctly under default and production-like configurations. + +**Requirements:** R2, R4, R5. + +**Files:** +- `apps/swift/scripts/capture-visual-screenshots.ts` +- `apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift` +- `apps/swift/Sources/PackRat/Shared/ErrorView.swift` +- `apps/swift/Sources/PackRat/Shared/VisualSampleData.swift` +- Feature screens under `apps/swift/Sources/PackRat/Features/**` + +**Approach:** +- Run or trigger the visual capture catalog for iOS, iPad, macOS, and watchOS. +- Review contact sheets manually for indefinite spinners, top-aligned empty/error states, missing icons, clipped forms, broken modals, or mislabeled auth/offline states. +- Fix reusable state components before touching individual screens unless a screen has a unique bug. + +**Test scenarios:** +- Guest and auth screenshot sets include Home, feature lists, details, forms, empty states, error/offline states, modals, menus, and disabled-feature behavior. +- Auth screenshots with production target do not show broad connection-needed states after successful login. +- Empty/error/loading states are centered and consistent where appropriate. + +**Verification:** +- Screenshot artifacts exist as individual images plus contact sheets. +- Visual screenshot test suite passes or reports specific expected skips. + +### U5. Focused app/API fixes and tests + +**Goal:** Fix real defects uncovered by U2-U4 and protect them with tests. + +**Requirements:** R2, R4, R5. + +**Files:** +- Determined by the reproduced defect. +- Likely areas: Swift auth/session handling, API response decoding, local/offline state routing, and E2E local worker parity with production responses. + +**Approach:** +- Characterize failing behavior first. +- Add or update the narrowest test that would have failed before the fix. +- Fix the app/API behavior rather than hiding failures in screenshot fixtures. +- Keep local E2E fixtures production-shaped enough to catch decoder and status-state bugs. + +**Test scenarios:** +- Any fixed loading bug has a unit, UI, or runner test that proves the expected settled state. +- Any API fixture change has a route test. +- Any auth classification change distinguishes unauthenticated, offline, and server-error states. + +**Verification:** +- Focused Swift unit/UI tests for changed surfaces. +- Focused API Vitest tests for changed API fixtures/routes. +- `bun run test:swift:scripts`. +- Touched-file lint/checks. + +### U6. Optional separate worktree/subagent for unrelated repo-wide failures + +**Goal:** Avoid blocking Swift readiness on unrelated checks while still capturing easy independent fixes. + +**Requirements:** R7. + +**Files:** +- Only files outside the Swift beta PR scope if the failure is clearly unrelated. + +**Approach:** +- If repo-wide pre-push checks fail on unrelated Expo/docs/packages, classify the failure. +- If it is easy and isolated, create a separate worktree/branch and fix it independently. +- If it is broad or product-risky, document it as unrelated residual and do not mix it into the Swift PR. + +**Test scenarios:** +- Swift PR can be reviewed without unrelated cleanup noise. +- Any separate cleanup branch has its own focused verification. + +**Verification:** +- Separate PR or durable note for unrelated residuals. +- Swift PR status remains focused on Swift beta readiness. + +## Dependencies and Risks + +- Production E2E requires valid production-safe `E2E_TEST_EMAIL` and `E2E_TEST_PASSWORD` secrets. Without them, the deployed workflow can only prove guest and unauthenticated behavior. +- GitHub-hosted macOS runners may not support every simulator/watchOS runtime needed for the full visual matrix; local machine artifacts remain acceptable for device coverage that GitHub cannot provide. +- Feature flags disabled in canonical config may expose incomplete surfaces under all-on. That is useful signal, not an automatic mandate to ship those features enabled. +- Resolving conflicts may reveal that `development` already changed API or Swift behavior in ways that invalidate earlier local screenshots. + +## Verification Plan + +- `git diff --check` +- `rg -n '<<<<<<<|=======|>>>>>>>'` +- `bun run test:swift:scripts` +- Focused API Vitest tests for changed API fixture/routes. +- Focused Swift unit/UI/E2E commands for changed Swift surfaces. +- Swift visual screenshot capture for available iOS, iPad, macOS, and watchOS targets. +- Manual GitHub workflow runs for production/default and, if practical, all-on feature flag profiles. +- `gh pr view 2627 --json mergeable,statusCheckRollup` + +## Implementation Notes + +- Use small gitmoji commits. +- Do not commit temporary all-on/all-off generated Swift flag files. +- Do not put secrets in logs, screenshots, workflow summaries, or committed artifacts. +- Prefer fixing reusable SwiftUI state/form components over one-off per-screen layout patches. From 1f982db48ede23a4849466e5415424a749b6dd16 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:34:14 -0600 Subject: [PATCH 13/81] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20satisfy=20PR=20ch?= =?UTF-8?q?ecks=20for=20Swift=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/expo/providers/index.web.tsx | 6 ++--- .../scripts/capture-visual-screenshots.ts | 24 +++++++++++-------- apps/swift/scripts/generate-swift-config.ts | 9 +++++-- packages/api/scripts/e2e-local-start.ts | 2 +- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/apps/expo/providers/index.web.tsx b/apps/expo/providers/index.web.tsx index 406317a70e..1f02de036f 100644 --- a/apps/expo/providers/index.web.tsx +++ b/apps/expo/providers/index.web.tsx @@ -24,10 +24,8 @@ export function Providers({ children }: { children: ReactNode }) { - <> - {children} - - + {children} + diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index 5aa9159727..a6cfa8e98c 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -17,7 +17,7 @@ import { basename, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { APP_CONFIG } from '@packrat/config/config'; import { nodeEnv } from '@packrat/env/node'; -import { fromZod } from '@packrat/guards'; +import { fromZod, isNumber, isObject, isString } from '@packrat/guards'; import { safeJsonParse, safeJsonStringify, sleep } from '@packrat/utils'; import { anyOf, @@ -96,6 +96,10 @@ const SECRET_BUILD_SETTING_RE = createRegExp( oneOrMore(charNotIn(' \t\n\r')), [globalFlag], ); +const E2E_LOCAL_TOKEN_RE = createRegExp(exactly('e2e-local.'), oneOrMore(charIn('A-F0-9')), [ + globalFlag, + caseInsensitive, +]); const XCODEBUILD_TIMEOUT_MS = durationFromEnv('PACKRAT_VISUAL_XCODEBUILD_TIMEOUT_MS', 30 * 60_000); const XCRESULT_EXPORT_TIMEOUT_MS = durationFromEnv('PACKRAT_XCRESULT_EXPORT_TIMEOUT_MS', 90_000); const AUTOMATION_MODE_TIMEOUT_MS = 10_000; @@ -349,7 +353,7 @@ function redactSecrets(output: string): string { if (!secret) continue; redacted = redacted.split(secret).join('[REDACTED]'); } - redacted = redacted.replace(/e2e-local\.[a-f0-9]{64}/gi, '[REDACTED_E2E_TOKEN]'); + redacted = redacted.replace(E2E_LOCAL_TOKEN_RE, '[REDACTED_E2E_TOKEN]'); redacted = redacted.replace(EMAIL_RE, '[REDACTED_EMAIL]'); redacted = redacted.replace(SECRET_BUILD_SETTING_RE, (match) => { const equalsIndex = match.indexOf('='); @@ -1539,7 +1543,7 @@ async function assertDeployedVisualAuthReady(input: { const response = await fetch(`${baseURL}/api/auth/sign-in/email`, { method: 'POST', headers: { accept: 'application/json', 'content-type': 'application/json' }, - body: JSON.stringify({ email, password }), + body: safeJsonStringify({ email, password }), }); if (!response.ok) { throw new Error( @@ -2201,15 +2205,15 @@ function addExistingArtifactSummaries( } function parsePlatformRunSummary(value: unknown): PlatformRunSummary | null { - if (!value || typeof value !== 'object') return null; + if (!isObject(value)) return null; const candidate = value as Record; if (!isPlatform(candidate.platform)) return null; if ( - typeof candidate.screenshotDir !== 'string' || - typeof candidate.coverageManifest !== 'string' || - typeof candidate.contactSheet !== 'string' || + !isString(candidate.screenshotDir) || + !isString(candidate.coverageManifest) || + !isString(candidate.contactSheet) || !Array.isArray(candidate.groupedContactSheets) || - !candidate.groupedContactSheets.every((entry) => typeof entry === 'string') + !candidate.groupedContactSheets.every((entry) => isString(entry)) ) { return null; } @@ -2218,13 +2222,13 @@ function parsePlatformRunSummary(value: unknown): PlatformRunSummary | null { platform: candidate.platform, screenshotDir: candidate.screenshotDir, screenshotCount: - typeof candidate.screenshotCount === 'number' && Number.isFinite(candidate.screenshotCount) + isNumber(candidate.screenshotCount) && Number.isFinite(candidate.screenshotCount) ? candidate.screenshotCount : countCapturedScreenshots(candidate.screenshotDir), coverageManifest: candidate.coverageManifest, contactSheet: candidate.contactSheet, groupedContactSheets: candidate.groupedContactSheets, - ...(typeof candidate.resultBundle === 'string' ? { resultBundle: candidate.resultBundle } : {}), + ...(isString(candidate.resultBundle) ? { resultBundle: candidate.resultBundle } : {}), ...(candidate.testSummary ? { testSummary: candidate.testSummary as TestSummary } : {}), }; } diff --git a/apps/swift/scripts/generate-swift-config.ts b/apps/swift/scripts/generate-swift-config.ts index 1189a1ce0f..1ab8209720 100644 --- a/apps/swift/scripts/generate-swift-config.ts +++ b/apps/swift/scripts/generate-swift-config.ts @@ -15,6 +15,8 @@ import { writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { APP_CONFIG } from '@packrat/config/config'; +import { isObject } from '@packrat/guards'; +import { safeJsonParse } from '@packrat/utils'; import { renderSwiftFeatureFlags } from './lib/config-codegen'; const __dir = dirname(fileURLToPath(import.meta.url)); @@ -38,7 +40,10 @@ function parseFeatureFlagOverrides(): Partial> const raw = process.env.PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES; if (!raw?.trim()) return {}; - const parsed = JSON.parse(raw) as Record; + const parsed = safeJsonParse(raw); + if (!isObject(parsed)) { + throw new Error('PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES must be a JSON object'); + } const knownFlags = new Set(Object.keys(APP_CONFIG.featureFlags)); const overrides: Partial> = {}; @@ -46,7 +51,7 @@ function parseFeatureFlagOverrides(): Partial> if (!knownFlags.has(key)) { throw new Error(`Unknown PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES key: ${key}`); } - if (typeof value !== 'boolean') { + if (value !== true && value !== false) { throw new Error(`PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES.${key} must be a boolean`); } overrides[key as FeatureFlagName] = value; diff --git a/packages/api/scripts/e2e-local-start.ts b/packages/api/scripts/e2e-local-start.ts index 76c4b753f4..10f8fbb186 100644 --- a/packages/api/scripts/e2e-local-start.ts +++ b/packages/api/scripts/e2e-local-start.ts @@ -156,7 +156,7 @@ const seedOutput = await run({ }, captureOutput: true, }); -const seededUserId = seedOutput.match(/\(id=([^)]+)\)/)?.[1]; +const seededUserId = seedOutput.split('(id=').at(1)?.split(')').at(0); if (!seededUserId) { throw new Error('Unable to resolve seeded E2E user id from db:seed:e2e-user output.'); } From ed5d651495fe2e1b7260a4fe9c6ec64b229467e6 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:35:30 -0600 Subject: [PATCH 14/81] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20use=20env=20shim?= =?UTF-8?q?=20in=20Swift=20scripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/scripts/generate-swift-config.ts | 5 +++-- apps/swift/scripts/upload-testflight.ts | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/swift/scripts/generate-swift-config.ts b/apps/swift/scripts/generate-swift-config.ts index 1ab8209720..5248242761 100644 --- a/apps/swift/scripts/generate-swift-config.ts +++ b/apps/swift/scripts/generate-swift-config.ts @@ -15,6 +15,7 @@ import { writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { APP_CONFIG } from '@packrat/config/config'; +import { nodeEnv } from '@packrat/env/node'; import { isObject } from '@packrat/guards'; import { safeJsonParse } from '@packrat/utils'; import { renderSwiftFeatureFlags } from './lib/config-codegen'; @@ -37,7 +38,7 @@ type FeatureFlags = typeof APP_CONFIG.featureFlags; type FeatureFlagName = keyof FeatureFlags; function parseFeatureFlagOverrides(): Partial> { - const raw = process.env.PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES; + const raw = nodeEnv.PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES; if (!raw?.trim()) return {}; const parsed = safeJsonParse(raw); @@ -61,7 +62,7 @@ function parseFeatureFlagOverrides(): Partial> } function featureFlagsForProfile(): FeatureFlags { - const profile = process.env.PACKRAT_SWIFT_FEATURE_FLAG_PROFILE ?? 'default'; + const profile = nodeEnv.PACKRAT_SWIFT_FEATURE_FLAG_PROFILE ?? 'default'; const entries = Object.entries(APP_CONFIG.featureFlags) as [FeatureFlagName, boolean][]; const baseFlags = profile === 'default' diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index c2e0a85a2b..aa4275a540 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -31,6 +31,7 @@ import { execFileSync } from 'node:child_process'; import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { nodeEnv } from '@packrat/env/node'; const SWIFT_DIR = new URL('..', import.meta.url).pathname; const PROJECT = join(SWIFT_DIR, 'PackRat.xcodeproj'); @@ -43,7 +44,14 @@ const SCHEME = STAGING ? 'PackRat-iOS-Staging' : 'PackRat-iOS'; const CONFIGURATION = STAGING ? 'Staging' : 'Release'; function req(name: string): string { - const v = process.env[name]; + const v = + name === 'APPLE_ID' + ? nodeEnv.APPLE_ID + : name === 'APPLE_APP_PASSWORD' + ? nodeEnv.APPLE_APP_PASSWORD + : name === 'APPLE_TEAM_ID' + ? nodeEnv.APPLE_TEAM_ID + : undefined; if (!v) { console.error(`Missing required env var: ${name}. See script header.`); process.exit(1); @@ -54,7 +62,7 @@ function req(name: string): string { const appleId = req('APPLE_ID'); const appPassword = req('APPLE_APP_PASSWORD'); const teamId = req('APPLE_TEAM_ID'); -const buildNumber = process.env.BUILD_NUMBER ?? String(Math.floor(Date.now() / 1000)); +const buildNumber = nodeEnv.BUILD_NUMBER ?? String(Math.floor(Date.now() / 1000)); const work = mkdtempSync(join(tmpdir(), 'packrat-tf-')); const archivePath = join(work, 'PackRat.xcarchive'); From c79a2ad29c903dbb26acb2d0290cebcf41b7fa9c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:39:11 -0600 Subject: [PATCH 15/81] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20remove=20Swift=20?= =?UTF-8?q?validation=20casts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts/capture-visual-screenshots.ts | 54 ++++++++++++------- apps/swift/scripts/generate-swift-config.ts | 26 ++++++--- packages/api/src/e2e-worker.ts | 8 ++- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index a6cfa8e98c..4e5fdfd7f1 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -17,7 +17,7 @@ import { basename, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { APP_CONFIG } from '@packrat/config/config'; import { nodeEnv } from '@packrat/env/node'; -import { fromZod, isNumber, isObject, isString } from '@packrat/guards'; +import { fromZod } from '@packrat/guards'; import { safeJsonParse, safeJsonStringify, sleep } from '@packrat/utils'; import { anyOf, @@ -1473,6 +1473,34 @@ const AttachmentManifestEntrySchema = z }) .passthrough(); const parseAttachmentManifest = fromZod(z.array(AttachmentManifestEntrySchema)); +const TestRefSchema = z.object({ + identifier: z.string(), + testName: z.string().optional(), + className: z.string().optional(), +}); +const TestSummarySchema = z.object({ + totalTestCount: z.number(), + passedTests: z.number(), + failedTests: z.number(), + skippedTests: z.number(), + expectedFailures: z.number(), + passed: z.number(), + failed: z.number(), + skipped: z.number(), + result: z.string(), + failingTests: z.array(TestRefSchema), +}); +const PlatformRunSummarySchema = z.object({ + platform: z.enum(['ios', 'ipad', 'macos', 'watch']), + screenshotDir: z.string(), + screenshotCount: z.number().optional(), + coverageManifest: z.string(), + contactSheet: z.string(), + groupedContactSheets: z.array(z.string()), + resultBundle: z.string().optional(), + testSummary: TestSummarySchema.optional(), +}); +const parsePlatformRunSummaryValue = fromZod(PlatformRunSummarySchema); function visualAuthMode(packratEnv: string): 'seeded' | 'real' { const explicit = Bun.env.PACKRAT_VISUAL_AUTH_MODE; @@ -2205,38 +2233,24 @@ function addExistingArtifactSummaries( } function parsePlatformRunSummary(value: unknown): PlatformRunSummary | null { - if (!isObject(value)) return null; - const candidate = value as Record; - if (!isPlatform(candidate.platform)) return null; - if ( - !isString(candidate.screenshotDir) || - !isString(candidate.coverageManifest) || - !isString(candidate.contactSheet) || - !Array.isArray(candidate.groupedContactSheets) || - !candidate.groupedContactSheets.every((entry) => isString(entry)) - ) { - return null; - } + const candidate = parsePlatformRunSummaryValue(value); + if (!candidate) return null; return { platform: candidate.platform, screenshotDir: candidate.screenshotDir, screenshotCount: - isNumber(candidate.screenshotCount) && Number.isFinite(candidate.screenshotCount) + candidate.screenshotCount !== undefined && Number.isFinite(candidate.screenshotCount) ? candidate.screenshotCount : countCapturedScreenshots(candidate.screenshotDir), coverageManifest: candidate.coverageManifest, contactSheet: candidate.contactSheet, groupedContactSheets: candidate.groupedContactSheets, - ...(isString(candidate.resultBundle) ? { resultBundle: candidate.resultBundle } : {}), - ...(candidate.testSummary ? { testSummary: candidate.testSummary as TestSummary } : {}), + ...(candidate.resultBundle ? { resultBundle: candidate.resultBundle } : {}), + ...(candidate.testSummary ? { testSummary: candidate.testSummary } : {}), }; } -function isPlatform(value: unknown): value is Platform { - return value === 'ios' || value === 'ipad' || value === 'macos' || value === 'watch'; -} - function countCapturedScreenshots(screenshotDir: string): number { if (!existsSync(screenshotDir)) return 0; return readdirSync(screenshotDir).filter((fileName) => fileName.endsWith('.png')).length; diff --git a/apps/swift/scripts/generate-swift-config.ts b/apps/swift/scripts/generate-swift-config.ts index 5248242761..4ed8c363ee 100644 --- a/apps/swift/scripts/generate-swift-config.ts +++ b/apps/swift/scripts/generate-swift-config.ts @@ -37,6 +37,18 @@ const outputs = [ type FeatureFlags = typeof APP_CONFIG.featureFlags; type FeatureFlagName = keyof FeatureFlags; +function isFeatureFlagName(value: string): value is FeatureFlagName { + return Object.hasOwn(APP_CONFIG.featureFlags, value); +} + +function featureFlagsWithValue(value: boolean): FeatureFlags { + const flags = { ...APP_CONFIG.featureFlags }; + for (const key of Object.keys(flags)) { + if (isFeatureFlagName(key)) flags[key] = value; + } + return flags; +} + function parseFeatureFlagOverrides(): Partial> { const raw = nodeEnv.PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES; if (!raw?.trim()) return {}; @@ -45,17 +57,16 @@ function parseFeatureFlagOverrides(): Partial> if (!isObject(parsed)) { throw new Error('PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES must be a JSON object'); } - const knownFlags = new Set(Object.keys(APP_CONFIG.featureFlags)); const overrides: Partial> = {}; for (const [key, value] of Object.entries(parsed)) { - if (!knownFlags.has(key)) { + if (!isFeatureFlagName(key)) { throw new Error(`Unknown PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES key: ${key}`); } if (value !== true && value !== false) { throw new Error(`PACKRAT_SWIFT_FEATURE_FLAG_OVERRIDES.${key} must be a boolean`); } - overrides[key as FeatureFlagName] = value; + overrides[key] = value; } return overrides; @@ -63,14 +74,13 @@ function parseFeatureFlagOverrides(): Partial> function featureFlagsForProfile(): FeatureFlags { const profile = nodeEnv.PACKRAT_SWIFT_FEATURE_FLAG_PROFILE ?? 'default'; - const entries = Object.entries(APP_CONFIG.featureFlags) as [FeatureFlagName, boolean][]; const baseFlags = profile === 'default' - ? Object.fromEntries(entries) + ? { ...APP_CONFIG.featureFlags } : profile === 'all-on' - ? Object.fromEntries(entries.map(([key]) => [key, true])) + ? featureFlagsWithValue(true) : profile === 'all-off' - ? Object.fromEntries(entries.map(([key]) => [key, false])) + ? featureFlagsWithValue(false) : undefined; if (!baseFlags) { @@ -82,7 +92,7 @@ function featureFlagsForProfile(): FeatureFlags { return { ...baseFlags, ...parseFeatureFlagOverrides(), - } as FeatureFlags; + }; } const featureFlags = featureFlagsForProfile(); diff --git a/packages/api/src/e2e-worker.ts b/packages/api/src/e2e-worker.ts index f3599c9290..dde861c742 100644 --- a/packages/api/src/e2e-worker.ts +++ b/packages/api/src/e2e-worker.ts @@ -18,6 +18,10 @@ function enrichEnv(env: Env): Env { return env; } +function envRecord(env: Env): Record { + return Object.fromEntries(Object.entries(env)); +} + function wellKnownMetadataKind(pathname: string): 'openid' | 'authorization-server' | null { if ( pathname === WELL_KNOWN_OPENID_CONFIG_PATH || @@ -37,8 +41,8 @@ function wellKnownMetadataKind(pathname: string): 'openid' | 'authorization-serv export default { // biome-ignore lint/complexity/useMaxParams: Cloudflare Worker fetch callbacks receive request, env, and context. async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { - const e = getEnv(enrichEnv(env) as unknown as Record); - setWorkerEnv(e as unknown as Record); + const e = getEnv(envRecord(enrichEnv(env))); + setWorkerEnv(envRecord(e)); const url = new URL(request.url); if (request.method === 'GET') { From 96f9593e22c387bd1d68dc1b681b60e365b8359e Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:42:43 -0600 Subject: [PATCH 16/81] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20satisfy=20owned?= =?UTF-8?q?=20api=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/scripts/lib/e2e-api.ts | 5 +- apps/swift/scripts/upload-testflight.ts | 100 ++++++++++--------- packages/api/src/routes/seasonSuggestions.ts | 16 ++- 3 files changed, 70 insertions(+), 51 deletions(-) diff --git a/apps/swift/scripts/lib/e2e-api.ts b/apps/swift/scripts/lib/e2e-api.ts index 47b6f8c33a..884d7ecbbc 100644 --- a/apps/swift/scripts/lib/e2e-api.ts +++ b/apps/swift/scripts/lib/e2e-api.ts @@ -26,7 +26,8 @@ async function isHealthy(baseURL: string): Promise { } } -async function waitForHealthy(baseURL: string, child: ChildProcess): Promise { +async function waitForHealthy(input: { baseURL: string; child: ChildProcess }): Promise { + const { baseURL, child } = input; const startedAt = Date.now(); let exited = false; let exitCode: number | null = null; @@ -100,7 +101,7 @@ export async function ensureLocalE2EAPI(input: { child.stderr?.on('data', (chunk) => process.stderr.write(chunk)); try { - await waitForHealthy(baseURL, child); + await waitForHealthy({ baseURL, child }); console.log(`✓ Local E2E API is healthy at ${baseURL}`); } catch (error) { await stopChild(child); diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index aa4275a540..038ee7ecd9 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -68,30 +68,34 @@ const work = mkdtempSync(join(tmpdir(), 'packrat-tf-')); const archivePath = join(work, 'PackRat.xcarchive'); const exportDir = join(work, 'export'); -function run(cmd: string, args: string[]) { +function run(input: { cmd: string; args: string[] }) { + const { cmd, args } = input; console.log(`\n$ ${cmd} ${args.join(' ')}`); execFileSync(cmd, args, { stdio: 'inherit' }); } // 1. Archive for a real device (TestFlight cannot accept a simulator build). -run('xcodebuild', [ - 'archive', - '-project', - PROJECT, - '-scheme', - SCHEME, - '-configuration', - CONFIGURATION, - '-destination', - 'generic/platform=iOS', - '-archivePath', - archivePath, - // Lets Xcode register the App IDs and generate provisioning profiles for - // the (new) bundle ids on the fly, using the signed-in account. - '-allowProvisioningUpdates', - `CURRENT_PROJECT_VERSION=${buildNumber}`, - `DEVELOPMENT_TEAM=${teamId}`, -]); +run({ + cmd: 'xcodebuild', + args: [ + 'archive', + '-project', + PROJECT, + '-scheme', + SCHEME, + '-configuration', + CONFIGURATION, + '-destination', + 'generic/platform=iOS', + '-archivePath', + archivePath, + // Lets Xcode register the App IDs and generate provisioning profiles for + // the (new) bundle ids on the fly, using the signed-in account. + '-allowProvisioningUpdates', + `CURRENT_PROJECT_VERSION=${buildNumber}`, + `DEVELOPMENT_TEAM=${teamId}`, + ], +}); // 2. Export a signed .ipa for App Store distribution. const exportOptions = join(work, 'ExportOptions.plist'); @@ -111,37 +115,43 @@ writeFileSync( `, ); -run('xcodebuild', [ - '-exportArchive', - '-archivePath', - archivePath, - '-exportPath', - exportDir, - '-exportOptionsPlist', - exportOptions, - // Export also needs to generate the App Store distribution profiles for the - // new bundle ids on the fly. - '-allowProvisioningUpdates', -]); +run({ + cmd: 'xcodebuild', + args: [ + '-exportArchive', + '-archivePath', + archivePath, + '-exportPath', + exportDir, + '-exportOptionsPlist', + exportOptions, + // Export also needs to generate the App Store distribution profiles for the + // new bundle ids on the fly. + '-allowProvisioningUpdates', + ], +}); // 3. Upload to TestFlight via altool (app-specific-password auth). // `--asc-provider` (team short name) is required when the Apple ID belongs to // more than one team, so altool knows which one to deliver to. const ipa = join(exportDir, `${SCHEME}.ipa`); -run('xcrun', [ - 'altool', - '--upload-app', - '--type', - 'ios', - '--file', - ipa, - '--username', - appleId, - '--password', - appPassword, - '--asc-provider', - teamId, -]); +run({ + cmd: 'xcrun', + args: [ + 'altool', + '--upload-app', + '--type', + 'ios', + '--file', + ipa, + '--username', + appleId, + '--password', + appPassword, + '--asc-provider', + teamId, + ], +}); console.log( `\n✓ Uploaded build ${buildNumber} to TestFlight (${BUNDLE_ID}, ${CONFIGURATION}` + diff --git a/packages/api/src/routes/seasonSuggestions.ts b/packages/api/src/routes/seasonSuggestions.ts index ca7b4599c2..00d4e7a216 100644 --- a/packages/api/src/routes/seasonSuggestions.ts +++ b/packages/api/src/routes/seasonSuggestions.ts @@ -13,9 +13,12 @@ import { DEFAULT_MODELS } from '../utils/ai/models'; const isE2EStubOpenAiKey = (openAiApiKey: string | undefined) => openAiApiKey?.startsWith('sk-e2e-stub-') === true; -const isLocalE2ESeasonSuggestionEnv = (openAiApiKey: string | undefined, databaseUrl: string) => - (isE2EStubOpenAiKey(openAiApiKey) || openAiApiKey === 'sk-test') && - (databaseUrl.includes('127.0.0.1') || databaseUrl.includes('localhost')); +const isLocalE2ESeasonSuggestionEnv = (input: { + openAiApiKey: string | undefined; + databaseUrl: string; +}) => + (isE2EStubOpenAiKey(input.openAiApiKey) || input.openAiApiKey === 'sk-test') && + (input.databaseUrl.includes('127.0.0.1') || input.databaseUrl.includes('localhost')); /** * Formats user inventory items for AI processing @@ -48,7 +51,12 @@ export const seasonSuggestionsRoutes = new Elysia({ prefix: '/season-suggestions NEON_DATABASE_URL, } = getEnv(); - if (isLocalE2ESeasonSuggestionEnv(OPENAI_API_KEY, NEON_DATABASE_URL)) { + if ( + isLocalE2ESeasonSuggestionEnv({ + openAiApiKey: OPENAI_API_KEY, + databaseUrl: NEON_DATABASE_URL, + }) + ) { const suggestionItems = [ { id: 'e2e-season-rain-shell', From 02d8c7fe1ea0a8ac5953251433780ec5630f139e Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:45:34 -0600 Subject: [PATCH 17/81] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20type=20e2e=20api?= =?UTF-8?q?=20env=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/env/src/node.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/env/src/node.ts b/packages/env/src/node.ts index e67148e786..c13efba9e5 100644 --- a/packages/env/src/node.ts +++ b/packages/env/src/node.ts @@ -87,6 +87,8 @@ export const nodeEnvSchema = z.object({ // ── OpenAI (packages/api/src/db/seed-e2e-catalog.ts) ────────────── OPENAI_API_KEY: z.string().min(1).optional(), + WEATHER_API_KEY: z.string().min(1).optional(), + APPLE_PRIVATE_KEY: z.string().min(1).optional(), E2E_API_URL: z.string().url().optional(), E2E_DB_URL: z.string().url().optional(), E2E_DB_PORT: z.string().regex(/^\d+$/, 'E2E_DB_PORT must be a numeric string').optional(), @@ -159,6 +161,8 @@ export const nodeEnv = nodeEnvSchema.parse({ E2E_TEST_EMAIL: process.env.E2E_TEST_EMAIL, E2E_TEST_PASSWORD: process.env.E2E_TEST_PASSWORD, OPENAI_API_KEY: process.env.OPENAI_API_KEY, + WEATHER_API_KEY: process.env.WEATHER_API_KEY, + APPLE_PRIVATE_KEY: process.env.APPLE_PRIVATE_KEY, E2E_API_URL: process.env.E2E_API_URL, E2E_DB_URL: process.env.E2E_DB_URL, E2E_DB_PORT: process.env.E2E_DB_PORT, From 6157e1d7f18bc87962c88c6a3bba8f82e66d6060 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 23:03:10 -0600 Subject: [PATCH 18/81] =?UTF-8?q?=F0=9F=A7=AA=20test:=20run=20iOS=20smoke?= =?UTF-8?q?=20through=20Swift=20wrapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-ci.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index 6b490df188..fceebf7ed4 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -79,14 +79,15 @@ jobs: E2E_EMAIL: ${{ secrets.E2E_EMAIL }} E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }} run: | + if [ "${{ matrix.scheme }}" = "PackRat-iOS" ]; then + bun run e2e:swift:ios-smoke + exit 0 + fi + cd apps/swift # Use signing-bypass on macOS so CI can build/test without a Mac Development cert # provisioned for the runner. iOS Simulator never needs signing. - if [ "${{ matrix.scheme }}" = "PackRat-macOS" ]; then - CODE_SIGN_ARGS=(CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO) - else - CODE_SIGN_ARGS=() - fi + CODE_SIGN_ARGS=(CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO) DESTINATION="${{ matrix.destination }}" if [ "${{ matrix.scheme }}" = "PackRat-iOS" ]; then DESTINATION="platform=iOS Simulator,id=${IOS_SIMULATOR_ID}" From 50a875f1203aae54efbcb28c6e304fefee4b55d6 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 23:10:53 -0600 Subject: [PATCH 19/81] =?UTF-8?q?=F0=9F=A7=AA=20test:=20default=20Swift=20?= =?UTF-8?q?CI=20local=20auth=20creds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index fceebf7ed4..b964b113a6 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -80,6 +80,10 @@ jobs: E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }} run: | if [ "${{ matrix.scheme }}" = "PackRat-iOS" ]; then + export E2E_EMAIL="${E2E_EMAIL:-e2e@packrattest.local}" + export E2E_PASSWORD="${E2E_PASSWORD:-E2eTestPass123!}" + export E2E_TEST_EMAIL="${E2E_TEST_EMAIL:-$E2E_EMAIL}" + export E2E_TEST_PASSWORD="${E2E_TEST_PASSWORD:-$E2E_PASSWORD}" bun run e2e:swift:ios-smoke exit 0 fi From ec46ac2355bb3d0b362aa979bb92705e2e346d77 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 23:16:25 -0600 Subject: [PATCH 20/81] =?UTF-8?q?=F0=9F=A7=AA=20test:=20make=20Swift=20iOS?= =?UTF-8?q?=20smoke=20self-contained=20in=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-ci.yml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index b964b113a6..c18499472a 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -79,19 +79,23 @@ jobs: E2E_EMAIL: ${{ secrets.E2E_EMAIL }} E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }} run: | - if [ "${{ matrix.scheme }}" = "PackRat-iOS" ]; then - export E2E_EMAIL="${E2E_EMAIL:-e2e@packrattest.local}" - export E2E_PASSWORD="${E2E_PASSWORD:-E2eTestPass123!}" - export E2E_TEST_EMAIL="${E2E_TEST_EMAIL:-$E2E_EMAIL}" - export E2E_TEST_PASSWORD="${E2E_TEST_PASSWORD:-$E2E_PASSWORD}" - bun run e2e:swift:ios-smoke - exit 0 - fi - cd apps/swift # Use signing-bypass on macOS so CI can build/test without a Mac Development cert - # provisioned for the runner. iOS Simulator never needs signing. - CODE_SIGN_ARGS=(CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO) + # provisioned for the runner. iOS Simulator never needs signing, but + # it does need the same PACKRAT_E2E_* build settings as the local + # wrapper so smoke auth does not depend on repository secrets. + if [ "${{ matrix.scheme }}" = "PackRat-iOS" ]; then + E2E_EMAIL="${E2E_EMAIL:-e2e@packrattest.local}" + E2E_PASSWORD="${E2E_PASSWORD:-E2eTestPass123!}" + CODE_SIGN_ARGS=( + "PACKRAT_E2E_EMAIL=${E2E_EMAIL}" + "PACKRAT_E2E_PASSWORD=${E2E_PASSWORD}" + "PACKRAT_E2E_ALLOW_LOGIN_SEED=1" + "PACKRAT_ENV=local" + ) + else + CODE_SIGN_ARGS=(CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO) + fi DESTINATION="${{ matrix.destination }}" if [ "${{ matrix.scheme }}" = "PackRat-iOS" ]; then DESTINATION="platform=iOS Simulator,id=${IOS_SIMULATOR_ID}" From 3bd847e045ec5442f5d4ca2ac15c142c443cce72 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 17 Jul 2026 23:51:01 -0600 Subject: [PATCH 21/81] =?UTF-8?q?=F0=9F=90=87=20fix:=20address=20Swift=20b?= =?UTF-8?q?eta=20review=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 12 +- .github/workflows/swift-visual.yml | 2 +- .../Sources/PackRat/Network/APIClient.swift | 8 + .../PackRat/Services/CatalogService.swift | 18 +- .../Tests/PackRatTests/OfflineAITests.swift | 3 +- .../VisualScreenshotTests.swift | 32 ++-- .../scripts/capture-visual-screenshots.ts | 160 ++++++++++-------- apps/swift/scripts/run-e2e-macos.ts | 5 +- apps/swift/scripts/run-e2e.ts | 5 +- packages/api/src/routes/catalog/index.ts | 13 +- packages/api/src/routes/guides/index.ts | 16 +- packages/api/src/routes/seasonSuggestions.ts | 12 +- packages/api/src/routes/weather.ts | 139 +-------------- packages/api/src/services/weatherService.ts | 141 ++++++++++++++- packages/api/src/utils/env-validation.ts | 19 +++ 15 files changed, 329 insertions(+), 256 deletions(-) diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index 37c66af619..191c98ea89 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -124,8 +124,10 @@ jobs: run: | target="${{ inputs.api_environment || 'configured-secret' }}" missing=() - [ -z "${E2E_EMAIL:-}" ] && missing+=("E2E_TEST_EMAIL") - [ -z "${E2E_PASSWORD:-}" ] && missing+=("E2E_TEST_PASSWORD") + if [ "$target" != "local" ]; then + [ -z "${E2E_EMAIL:-}" ] && missing+=("E2E_TEST_EMAIL") + [ -z "${E2E_PASSWORD:-}" ] && missing+=("E2E_TEST_PASSWORD") + fi if [ "$target" = "configured-secret" ] && [ -z "${E2E_API_BASE_URL:-}" ]; then missing+=("SWIFT_E2E_API_BASE_URL") fi @@ -252,8 +254,10 @@ jobs: run: | target="${{ inputs.api_environment || 'configured-secret' }}" missing=() - [ -z "${E2E_EMAIL:-}" ] && missing+=("E2E_TEST_EMAIL") - [ -z "${E2E_PASSWORD:-}" ] && missing+=("E2E_TEST_PASSWORD") + if [ "$target" != "local" ]; then + [ -z "${E2E_EMAIL:-}" ] && missing+=("E2E_TEST_EMAIL") + [ -z "${E2E_PASSWORD:-}" ] && missing+=("E2E_TEST_PASSWORD") + fi if [ "$target" = "configured-secret" ] && [ -z "${E2E_API_BASE_URL:-}" ]; then missing+=("SWIFT_E2E_API_BASE_URL") fi diff --git a/.github/workflows/swift-visual.yml b/.github/workflows/swift-visual.yml index 437f06c0e5..35777b05e3 100644 --- a/.github/workflows/swift-visual.yml +++ b/.github/workflows/swift-visual.yml @@ -137,7 +137,7 @@ jobs: run: bun swift - name: Boot iOS simulator - if: ${{ (github.event.inputs.platform || 'all') != 'macos' }} + if: ${{ (github.event.inputs.platform || 'all') == 'ios' || (github.event.inputs.platform || 'all') == 'all' || (github.event.inputs.platform || 'all') == 'both' }} run: | DEVICE_ID=$(xcrun simctl create Swift-Visual "iPhone 17" \ "$(xcrun simctl list runtimes -j | jq -r '.runtimes[] | select(.identifier | contains("iOS-")) | .identifier' | sort -V | tail -1)") diff --git a/apps/swift/Sources/PackRat/Network/APIClient.swift b/apps/swift/Sources/PackRat/Network/APIClient.swift index 0156cfdc70..2c16d1b51e 100644 --- a/apps/swift/Sources/PackRat/Network/APIClient.swift +++ b/apps/swift/Sources/PackRat/Network/APIClient.swift @@ -51,6 +51,14 @@ actor APIClient { return try await execute(request, as: T.self) } + func sendData(_ endpoint: some APIEndpoint) async throws -> Data { + let request = try buildRequest(endpoint, sessionToken: KeychainService.shared.sessionToken) + let (data, response) = try await dataWithTransientRetry(for: request) + captureSessionTokenIfPresent(response) + try validateStatus(response, data: data) + return data + } + func sendDiscarding(_ endpoint: some APIEndpoint) async throws { let request = try buildRequest(endpoint, sessionToken: KeychainService.shared.sessionToken) let (data, response) = try await dataWithTransientRetry(for: request) diff --git a/apps/swift/Sources/PackRat/Services/CatalogService.swift b/apps/swift/Sources/PackRat/Services/CatalogService.swift index df126da50f..45ca9e7856 100644 --- a/apps/swift/Sources/PackRat/Services/CatalogService.swift +++ b/apps/swift/Sources/PackRat/Services/CatalogService.swift @@ -12,18 +12,22 @@ final class CatalogService: Sendable { "page": "\(page)", "limit": "\(limit)", ]) - // Handle both wrapped and unwrapped responses - if let wrapped = try? await api.send(endpoint, as: CatalogSearchResponse.self) { - return wrapped.items - } - return try await api.send(endpoint) + let data = try await api.sendData(endpoint) + return try decodeCatalogItems(from: data) } func semanticSearch(query: String, limit: Int = 10) async throws -> [CatalogItem] { let endpoint = Endpoint(.get, "/api/catalog/vector-search", query: ["q": query, "limit": "\(limit)"]) - if let wrapped = try? await api.send(endpoint, as: CatalogSearchResponse.self) { + let data = try await api.sendData(endpoint) + return try decodeCatalogItems(from: data) + } + + private func decodeCatalogItems(from data: Data) throws -> [CatalogItem] { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + if let wrapped = try? decoder.decode(CatalogSearchResponse.self, from: data) { return wrapped.items } - return try await api.send(endpoint) + return try decoder.decode([CatalogItem].self, from: data) } } diff --git a/apps/swift/Tests/PackRatTests/OfflineAITests.swift b/apps/swift/Tests/PackRatTests/OfflineAITests.swift index 33e3c7e06d..95bef0e4dc 100644 --- a/apps/swift/Tests/PackRatTests/OfflineAITests.swift +++ b/apps/swift/Tests/PackRatTests/OfflineAITests.swift @@ -267,8 +267,9 @@ struct OfflineAIViewModelTests { struct LocalLLMProviderFactoryTests { @Test("uses mock provider even when stale MLX debug preference is enabled") func staleMLXPreferenceUsesMockProvider() { + let previousValue = Defaults[.useRealLocalLLM] Defaults[.useRealLocalLLM] = true - defer { Defaults[.useRealLocalLLM] = false } + defer { Defaults[.useRealLocalLLM] = previousValue } let provider = LocalLLMProviderFactory.makeProvider() diff --git a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift index acfff8c2f9..6738ff58ea 100644 --- a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift +++ b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift @@ -591,10 +591,11 @@ final class VisualScreenshotTests: XCTestCase { } } - private func captureTab(_ label: String, name: String) { + private func captureTab(_ label: String, name: String, beforeCapture: (() -> Void)? = nil) { let tab = app.tabBars.buttons[label] XCTAssertTrue(tab.waitForExistence(timeout: 5), "Expected tab '\(label)' for screenshot \(name)") tab.tap() + beforeCapture?() capture(name) } @@ -602,7 +603,8 @@ final class VisualScreenshotTests: XCTestCase { _ title: String, name: String, dismissAfterCapture: Bool = true, - destinationIdentifier: String? = nil + destinationIdentifier: String? = nil, + beforeCapture: (() -> Void)? = nil ) { let baselineName = name.hasPrefix("home-before-") ? name : "home-before-\(name)" openHomeForActionBaseline(name: baselineName) @@ -615,6 +617,7 @@ final class VisualScreenshotTests: XCTestCase { "Expected Home action '\(title)' to open '\(destinationIdentifier)' for screenshot \(name)" ) } + beforeCapture?() self.capture(name) if dismissAfterCapture { self.dismissPhoneDestination() @@ -786,14 +789,19 @@ final class VisualScreenshotTests: XCTestCase { } private func captureGuestLimitedHomeAction(_ title: String, name: String) { - captureHomeAction(title, name: name, dismissAfterCapture: false) - assertExpectedAccountRequiredState(for: name) + captureHomeAction( + title, + name: name, + dismissAfterCapture: false, + beforeCapture: { self.assertExpectedAccountRequiredState(for: name) } + ) dismissPhoneDestination() } private func captureGuestLimitedTab(_ label: String, name: String) { - captureTab(label, name: name) - assertExpectedAccountRequiredState(for: name) + captureTab(label, name: name) { + self.assertExpectedAccountRequiredState(for: name) + } } private func capturePhoneGuestAccountLimits() { @@ -934,19 +942,19 @@ final class VisualScreenshotTests: XCTestCase { for (label, name) in entries { selectSidebar(label) - capture(name) assertExpectedAccountRequiredState(for: name) + capture(name) } if UITestFeatureFlags.enableFeed { selectSidebar("Feed") - capture("50-guest-limit-feed") assertExpectedAccountRequiredState(for: "50-guest-limit-feed") + capture("50-guest-limit-feed") } if UITestFeatureFlags.enableWildlifeIdentification { selectSidebar("Wildlife") - capture("50-guest-limit-wildlife") assertExpectedAccountRequiredState(for: "50-guest-limit-wildlife") + capture("50-guest-limit-wildlife") } } @@ -1281,9 +1289,11 @@ final class VisualScreenshotTests: XCTestCase { private func launchAuthenticated(sampleData: Bool = false, forceOffline: Bool = false) { let bundle = Bundle(for: VisualScreenshotTests.self) - let email = (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_EMAIL") as? String) + let email = ProcessInfo.processInfo.environment["PACKRAT_E2E_EMAIL"] + ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_EMAIL") as? String) ?? "e2e@packrat.test" - let userId = ProcessInfo.processInfo.environment["E2E_TEST_USER_ID"] + let userId = ProcessInfo.processInfo.environment["PACKRAT_E2E_USER_ID"] + ?? ProcessInfo.processInfo.environment["E2E_TEST_USER_ID"] ?? (bundle.object(forInfoDictionaryKey: "PACKRAT_E2E_USER_ID") as? String) ?? "00000000-0000-4000-8000-000000000001" diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index 4e5fdfd7f1..6207a33df2 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -941,7 +941,7 @@ async function runXcodeVisualTest( `PACKRAT_VISUAL_AUTH_MODE=${authMode}`, `PACKRAT_VISUAL_PLATFORM=${platform}`, ]; - injectVisualSchemeEnvironment(platform, { + const restoreVisualScheme = injectVisualSchemeEnvironment(platform, { E2E_API_BASE_URL: apiBaseURL, PACKRAT_ENV: packratEnv, PACKRAT_SCREENSHOT_DIR: writableScreenshotDir, @@ -988,85 +988,98 @@ async function runXcodeVisualTest( console.log(`→ XCTest write dir: ${writableScreenshotDir}`); console.log(`→ Result bundle: ${resultBundle}`); - await assertDeployedVisualAuthReady({ packratEnv, apiBaseURL, authMode }); + try { + await assertDeployedVisualAuthReady({ packratEnv, apiBaseURL, authMode }); - if (platform === 'macos') { - assertAutomationModeAvailable(); - clearMacNotificationBanners(); - } + if (platform === 'macos') { + assertAutomationModeAvailable(); + clearMacNotificationBanners(); + } - return new Promise((resolvePromise, reject) => { - let timedOut = false; - let finalized = false; - const child = spawn('xcodebuild', args, { - cwd: SWIFT_DIR, - env: { - ...Bun.env, - E2E_API_BASE_URL: apiBaseURL, - PACKRAT_ENV: packratEnv, - PACKRAT_VISUAL_AUTH_MODE: authMode, - PACKRAT_SCREENSHOT_DIR: writableScreenshotDir, - PACKRAT_VISUAL_PLATFORM: platform, - }, - }); - const timeout = setTimeout(() => { - timedOut = true; - console.error( - `xcodebuild timed out after ${Math.round(XCODEBUILD_TIMEOUT_MS / 1000)}s for ${platform}; terminating child process.`, - ); - child.kill('SIGINT'); - setTimeout(() => { - if (!child.killed) child.kill('SIGKILL'); - }, 5_000).unref(); - }, XCODEBUILD_TIMEOUT_MS); - timeout.unref(); - - child.stdout.on('data', (chunk) => process.stdout.write(redactSecrets(chunk.toString()))); - child.stderr.on('data', (chunk) => process.stderr.write(redactSecrets(chunk.toString()))); - child.on('error', (err) => { - if (finalized) return; - finalized = true; - clearTimeout(timeout); - reject(err); - }); - const finalize = (code: number | null) => { - if (finalized) return; - finalized = true; - clearTimeout(timeout); - try { - const summary = summarizeResult(resultBundle); - copyScreenshots(writableScreenshotDir, screenshotDir); - if (listScreenshots(screenshotDir).length === 0) { - exportScreenshotsFromResultBundle(resultBundle, screenshotDir); - } - if (code === 0) { - resolvePromise({ resultBundle, summary }); + return await new Promise((resolvePromise, reject) => { + let timedOut = false; + let finalized = false; + const child = spawn('xcodebuild', args, { + cwd: SWIFT_DIR, + env: { + ...Bun.env, + E2E_API_BASE_URL: apiBaseURL, + PACKRAT_ENV: packratEnv, + PACKRAT_VISUAL_AUTH_MODE: authMode, + PACKRAT_SCREENSHOT_DIR: writableScreenshotDir, + PACKRAT_VISUAL_PLATFORM: platform, + }, + }); + const timeout = setTimeout(() => { + timedOut = true; + console.error( + `xcodebuild timed out after ${Math.round(XCODEBUILD_TIMEOUT_MS / 1000)}s for ${platform}; terminating child process.`, + ); + child.kill('SIGINT'); + setTimeout(() => { + if (!child.killed) child.kill('SIGKILL'); + }, 5_000).unref(); + }, XCODEBUILD_TIMEOUT_MS); + timeout.unref(); + + child.stdout.on('data', (chunk) => process.stdout.write(redactSecrets(chunk.toString()))); + child.stderr.on('data', (chunk) => process.stderr.write(redactSecrets(chunk.toString()))); + child.on('error', (err) => { + if (finalized) return; + finalized = true; + clearTimeout(timeout); + reject(err); + }); + const finalize = (code: number | null) => { + if (finalized) return; + finalized = true; + clearTimeout(timeout); + try { + const summary = summarizeResult(resultBundle); + copyScreenshots(writableScreenshotDir, screenshotDir); + if (listScreenshots(screenshotDir).length === 0) { + exportScreenshotsFromResultBundle(resultBundle, screenshotDir); + } + if (code === 0) { + resolvePromise({ resultBundle, summary }); + return; + } + } catch (err) { + reject(err); return; } - } catch (err) { - reject(err); - return; - } - if (timedOut) { - reject( - new Error( - `xcodebuild timed out after ${Math.round(XCODEBUILD_TIMEOUT_MS / 1000)}s for ${platform}`, - ), - ); - } else { - reject(new Error(`xcodebuild exited with ${code ?? 'unknown status'} for ${platform}`)); - } - }; - child.on('exit', finalize); - child.on('close', finalize); - }); + if (timedOut) { + reject( + new Error( + `xcodebuild timed out after ${Math.round(XCODEBUILD_TIMEOUT_MS / 1000)}s for ${platform}`, + ), + ); + } else { + reject(new Error(`xcodebuild exited with ${code ?? 'unknown status'} for ${platform}`)); + } + }; + child.on('exit', finalize); + child.on('close', finalize); + }); + } finally { + restoreVisualScheme(); + } } -function injectVisualSchemeEnvironment(platform: Platform, env: Record): void { +function injectVisualSchemeEnvironment( + platform: Platform, + env: Record, +): () => void { const schemePath = platform === 'macos' ? MACOS_SCHEME_PATH : IOS_SCHEME_PATH; - if (!existsSync(schemePath)) return; + if (!existsSync(schemePath)) return () => {}; - let content = readFileSync(schemePath, 'utf8'); + const originalContent = readFileSync(schemePath, 'utf8'); + let content = originalContent; + const restore = () => { + if (existsSync(schemePath) && readFileSync(schemePath, 'utf8') !== originalContent) { + writeFileSync(schemePath, originalContent); + } + }; content = removeEnvironmentVariablesBlock(content); content = content.replace( 'shouldUseLaunchSchemeArgsEnv = "YES"', @@ -1078,7 +1091,7 @@ function injectVisualSchemeEnvironment(platform: Platform, env: Record environmentVariableXml(key, value)); if (variables.length === 0) { writeFileSync(schemePath, content); - return; + return restore; } const block = [ @@ -1088,6 +1101,7 @@ function injectVisualSchemeEnvironment(platform: Platform, env: Record', `${block} `)); + return restore; } function buildSettingsToEnv(settings: string[]): Record { diff --git a/apps/swift/scripts/run-e2e-macos.ts b/apps/swift/scripts/run-e2e-macos.ts index 98d16b8947..de15de2834 100644 --- a/apps/swift/scripts/run-e2e-macos.ts +++ b/apps/swift/scripts/run-e2e-macos.ts @@ -135,10 +135,7 @@ function escapeXml(s: string): string { function deriveLocalE2ESessionToken(): string | undefined { if (PACKRAT_ENV !== 'local' && PACKRAT_ENV !== 'dev-local') return undefined; - const secret = - process.env.PACKRAT_AUTH_SECRET ?? - process.env.BETTER_AUTH_SECRET ?? - 'e2e-better-auth-secret-at-least-32-chars'; + const secret = process.env.BETTER_AUTH_SECRET ?? 'e2e-better-auth-secret-at-least-32-chars'; const email = process.env.E2E_TEST_EMAIL?.toLowerCase(); const userId = process.env.E2E_TEST_USER_ID; if (!email || !userId) return undefined; diff --git a/apps/swift/scripts/run-e2e.ts b/apps/swift/scripts/run-e2e.ts index cd014bb6e6..fa82288f2a 100644 --- a/apps/swift/scripts/run-e2e.ts +++ b/apps/swift/scripts/run-e2e.ts @@ -117,10 +117,7 @@ function escapeXml(s: string): string { function deriveLocalE2ESessionToken(): string | undefined { if (PACKRAT_ENV !== 'local' && PACKRAT_ENV !== 'dev-local') return undefined; - const secret = - process.env.PACKRAT_AUTH_SECRET ?? - process.env.BETTER_AUTH_SECRET ?? - 'e2e-better-auth-secret-at-least-32-chars'; + const secret = process.env.BETTER_AUTH_SECRET ?? 'e2e-better-auth-secret-at-least-32-chars'; const email = process.env.E2E_TEST_EMAIL?.toLowerCase(); const userId = process.env.E2E_TEST_USER_ID; if (!email || !userId) return undefined; diff --git a/packages/api/src/routes/catalog/index.ts b/packages/api/src/routes/catalog/index.ts index 963d48d011..861d3834f6 100644 --- a/packages/api/src/routes/catalog/index.ts +++ b/packages/api/src/routes/catalog/index.ts @@ -6,7 +6,8 @@ import { queueCatalogETL } from '@packrat/api/services/etl/queue'; import { R2BucketService } from '@packrat/api/services/r2-bucket'; import { buildInstanceId } from '@packrat/api/utils/buildInstanceId'; import { getEmbeddingText } from '@packrat/api/utils/embeddingHelper'; -import { getEnv } from '@packrat/api/utils/env-validation'; +import { getEnv, isLocalE2EApiEnv } from '@packrat/api/utils/env-validation'; +import { captureApiException } from '@packrat/api/utils/sentry'; import type { CatalogEtlWorkflowParams } from '@packrat/api/workflows/catalog-etl-workflow'; import { type ChunkSpec, chunkCsvForR2 } from '@packrat/api/workflows/shared/chunkCsvForR2'; import { catalogItems, etlJobs, packItems } from '@packrat/db'; @@ -40,10 +41,11 @@ import { z } from 'zod'; const isLocalE2ECatalogEnv = () => { const { NEON_DATABASE_URL, OPENAI_API_KEY } = getEnv(); - return ( - (OPENAI_API_KEY === 'sk-test' || OPENAI_API_KEY?.startsWith('sk-e2e-stub-') === true) && - (NEON_DATABASE_URL.includes('127.0.0.1') || NEON_DATABASE_URL.includes('localhost')) - ); + return isLocalE2EApiEnv({ + databaseUrl: NEON_DATABASE_URL, + openAiApiKey: OPENAI_API_KEY, + requireStubOpenAI: true, + }); }; const localE2ECatalogItems = [ @@ -202,6 +204,7 @@ export const catalogRoutes = new Elysia({ prefix: '/catalog' }) const catalogService = new CatalogService(); return await catalogService.vectorSearch({ q: searchQuery, opts: { limit, offset } }); } catch (error) { + captureApiException({ error, operation: 'catalog.vectorSearch' }); console.error('Vector search error:', error); return status(500, { error: 'Failed to search catalog items' }); } diff --git a/packages/api/src/routes/guides/index.ts b/packages/api/src/routes/guides/index.ts index 07409baaf0..cfc4383757 100644 --- a/packages/api/src/routes/guides/index.ts +++ b/packages/api/src/routes/guides/index.ts @@ -1,6 +1,7 @@ import { authPlugin } from '@packrat/api/middleware/auth'; import { R2BucketService } from '@packrat/api/services/r2-bucket'; -import { getEnv } from '@packrat/api/utils/env-validation'; +import { getEnv, isLocalE2EApiEnv } from '@packrat/api/utils/env-validation'; +import { captureApiException } from '@packrat/api/utils/sentry'; import { asNumber, asString, isArray } from '@packrat/guards'; import { GuideCategoriesResponseSchema, @@ -18,10 +19,11 @@ const MDX_EXT_RE = /\.(mdx?|md)$/; const DASH_RE = /-/g; const isLocalE2EGuidesEnv = () => { const { E2E_TEST_USER_ID, NEON_DATABASE_URL } = getEnv(); - return ( - Boolean(E2E_TEST_USER_ID) && - (NEON_DATABASE_URL.includes('127.0.0.1') || NEON_DATABASE_URL.includes('localhost')) - ); + return isLocalE2EApiEnv({ + databaseUrl: NEON_DATABASE_URL, + e2eUserId: E2E_TEST_USER_ID, + requireE2EUser: true, + }); }; const localE2EGuides = [ @@ -201,6 +203,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) totalPages, }); } catch (error) { + captureApiException({ error, operation: 'guides.list' }); console.error('Error listing guides:', error); throw error; } @@ -263,6 +266,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) const categories = Array.from(categoriesSet).sort(); return GuideCategoriesResponseSchema.parse({ categories, count: categories.length }); } catch (error) { + captureApiException({ error, operation: 'guides.categories' }); console.error('Error getting guide categories:', error); throw error; } @@ -386,6 +390,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) query: q, }); } catch (error) { + captureApiException({ error, operation: 'guides.search' }); console.error('Error searching guides:', error); throw error; } @@ -451,6 +456,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) updatedAt: object.uploaded.toISOString(), }); } catch (error) { + captureApiException({ error, operation: 'guides.detail' }); console.error('Error fetching guide:', error); throw error; } diff --git a/packages/api/src/routes/seasonSuggestions.ts b/packages/api/src/routes/seasonSuggestions.ts index 00d4e7a216..201f583a9e 100644 --- a/packages/api/src/routes/seasonSuggestions.ts +++ b/packages/api/src/routes/seasonSuggestions.ts @@ -1,7 +1,7 @@ import { createDb } from '@packrat/api/db'; import { authPlugin } from '@packrat/api/middleware/auth'; import { createAIProvider } from '@packrat/api/utils/ai/provider'; -import { getEnv } from '@packrat/api/utils/env-validation'; +import { getEnv, isLocalE2EApiEnv } from '@packrat/api/utils/env-validation'; import { type PackItem, packItems } from '@packrat/db'; import { SeasonSuggestionsRequestSchema } from '@packrat/schemas/seasonSuggestions'; import { generateObject } from 'ai'; @@ -10,15 +10,15 @@ import { Elysia, status } from 'elysia'; import { z } from 'zod'; import { DEFAULT_MODELS } from '../utils/ai/models'; -const isE2EStubOpenAiKey = (openAiApiKey: string | undefined) => - openAiApiKey?.startsWith('sk-e2e-stub-') === true; - const isLocalE2ESeasonSuggestionEnv = (input: { openAiApiKey: string | undefined; databaseUrl: string; }) => - (isE2EStubOpenAiKey(input.openAiApiKey) || input.openAiApiKey === 'sk-test') && - (input.databaseUrl.includes('127.0.0.1') || input.databaseUrl.includes('localhost')); + isLocalE2EApiEnv({ + databaseUrl: input.databaseUrl, + openAiApiKey: input.openAiApiKey, + requireStubOpenAI: true, + }); /** * Formats user inventory items for AI processing diff --git a/packages/api/src/routes/weather.ts b/packages/api/src/routes/weather.ts index eeb801425c..7584d5a0af 100644 --- a/packages/api/src/routes/weather.ts +++ b/packages/api/src/routes/weather.ts @@ -1,4 +1,9 @@ import { authPlugin } from '@packrat/api/middleware/auth'; +import { + buildStubForecast, + defaultStubLocation, + searchStubLocations, +} from '@packrat/api/services/weatherService'; import { getEnv } from '@packrat/api/utils/env-validation'; import { captureApiException } from '@packrat/api/utils/sentry'; import { isString } from '@packrat/guards'; @@ -17,143 +22,9 @@ import { Elysia, status } from 'elysia'; import { ZodError } from 'zod'; const WEATHER_API_BASE_URL = 'https://api.weatherapi.com/v1'; -const COORDINATE_QUERY_REGEX = /^-?\d+(\.\d+)?,-?\d+(\.\d+)?$/; const isE2EWeatherStubKey = (key?: string) => key?.startsWith('weather-e2e-stub-') === true; -const stubCondition = { - text: 'Partly cloudy', - icon: '//cdn.weatherapi.com/weather/64x64/day/116.png', - code: 1003, -}; - -const buildStubCurrent = (tempF = 72): WeatherAPIForecastResponse['current'] => ({ - last_updated: '2026-07-16 12:00', - temp_c: Math.round(((tempF - 32) * 5) / 9), - temp_f: tempF, - condition: stubCondition, - wind_mph: 7, - wind_kph: 11, - wind_degree: 240, - wind_dir: 'WSW', - pressure_mb: 1016, - pressure_in: 30.0, - precip_mm: 0, - precip_in: 0, - humidity: 32, - cloud: 25, - feelslike_c: Math.round(((tempF - 32) * 5) / 9), - feelslike_f: tempF, - vis_km: 16, - vis_miles: 10, - uv: 5, - is_day: 1, - gust_mph: 14, - gust_kph: 22, -}); - -const buildStubForecastDay = ( - dayOffset: number, -): WeatherAPIForecastResponse['forecast']['forecastday'][number] => { - const date = new Date(Date.UTC(2026, 6, 16 + dayOffset)); - const highF = 78 + (dayOffset % 3); - const lowF = 55 + (dayOffset % 2); - return { - date: date.toISOString().slice(0, 10), - date_epoch: Math.floor(date.getTime() / 1000), - day: { - maxtemp_c: Math.round(((highF - 32) * 5) / 9), - maxtemp_f: highF, - mintemp_c: Math.round(((lowF - 32) * 5) / 9), - mintemp_f: lowF, - avgtemp_c: Math.round((((highF + lowF) / 2 - 32) * 5) / 9), - avgtemp_f: (highF + lowF) / 2, - maxwind_mph: 18, - maxwind_kph: 29, - totalprecip_mm: dayOffset === 2 ? 1.2 : 0, - totalprecip_in: dayOffset === 2 ? 0.05 : 0, - totalsnow_cm: 0, - avghumidity: 35, - avgvis_km: 16, - avgvis_miles: 10, - uv: 5, - condition: stubCondition, - daily_chance_of_rain: dayOffset === 2 ? 30 : 5, - daily_chance_of_snow: 0, - }, - astro: { - sunrise: '05:48 AM', - sunset: '08:27 PM', - moonrise: '10:15 PM', - moonset: '07:12 AM', - moon_phase: 'Waxing Crescent', - moon_illumination: 18, - }, - hour: [], - }; -}; - -type StubWeatherLocation = WeatherAPISearchResponse[number]; - -const defaultStubLocation: StubWeatherLocation = { - id: 5419384, - name: 'Denver', - region: 'Colorado', - country: 'United States', - lat: 39.74, - lon: -104.98, - url: 'denver-colorado-united-states-of-america', -}; - -const stubLocations: [StubWeatherLocation, ...StubWeatherLocation[]] = [ - defaultStubLocation, - { - id: 5400000, - name: 'Yosemite Valley', - region: 'California', - country: 'United States', - lat: 37.75, - lon: -119.59, - url: 'yosemite-valley-california-united-states-of-america', - }, -] satisfies [StubWeatherLocation, ...StubWeatherLocation[]]; - -const getStubLocation = (id?: number) => - stubLocations.find((location) => location.id === id) ?? defaultStubLocation; - -const buildStubForecast = (id?: number): WeatherAPIForecastResponse => { - const location = getStubLocation(id); - return { - location: { - id: location.id, - name: location.name, - region: location.region, - country: location.country, - lat: location.lat, - lon: location.lon, - tz_id: location.name === 'Denver' ? 'America/Denver' : 'America/Los_Angeles', - localtime_epoch: 1784246400, - localtime: '2026-07-16 12:00', - }, - current: buildStubCurrent(location.name === 'Denver' ? 72 : 68), - forecast: { - forecastday: Array.from({ length: 10 }, (_, index) => buildStubForecastDay(index)), - }, - alerts: { alert: [] }, - }; -}; - -const searchStubLocations = (query: string) => { - const normalizedQuery = query.trim().toLowerCase(); - if (!normalizedQuery) return []; - if (COORDINATE_QUERY_REGEX.test(normalizedQuery)) return [defaultStubLocation]; - return stubLocations.filter((location) => - `${location.name} ${location.region} ${location.country}` - .toLowerCase() - .includes(normalizedQuery), - ); -}; - export const weatherRoutes = new Elysia({ prefix: '/weather' }) .model({ 'weather.ForecastResponse': WeatherAPIForecastResponseSchema, diff --git a/packages/api/src/services/weatherService.ts b/packages/api/src/services/weatherService.ts index 23e20f5fc2..55b5c79cdb 100644 --- a/packages/api/src/services/weatherService.ts +++ b/packages/api/src/services/weatherService.ts @@ -1,6 +1,145 @@ import { getEnv } from '@packrat/api/utils/env-validation'; import { captureApiException } from '@packrat/api/utils/sentry'; -import { OpenWeatherResponseSchema } from '@packrat/schemas/weather'; +import { + OpenWeatherResponseSchema, + type WeatherAPIForecastResponse, + type WeatherAPISearchResponse, +} from '@packrat/schemas/weather'; + +const COORDINATE_QUERY_REGEX = /^-?\d+(\.\d+)?,-?\d+(\.\d+)?$/; + +const stubCondition = { + text: 'Partly cloudy', + icon: '//cdn.weatherapi.com/weather/64x64/day/116.png', + code: 1003, +}; + +const buildStubCurrent = (tempF = 72): WeatherAPIForecastResponse['current'] => ({ + last_updated: '2026-07-16 12:00', + temp_c: Math.round(((tempF - 32) * 5) / 9), + temp_f: tempF, + condition: stubCondition, + wind_mph: 7, + wind_kph: 11, + wind_degree: 240, + wind_dir: 'WSW', + pressure_mb: 1016, + pressure_in: 30.0, + precip_mm: 0, + precip_in: 0, + humidity: 32, + cloud: 25, + feelslike_c: Math.round(((tempF - 32) * 5) / 9), + feelslike_f: tempF, + vis_km: 16, + vis_miles: 10, + uv: 5, + is_day: 1, + gust_mph: 14, + gust_kph: 22, +}); + +const buildStubForecastDay = ( + dayOffset: number, +): WeatherAPIForecastResponse['forecast']['forecastday'][number] => { + const date = new Date(Date.UTC(2026, 6, 16 + dayOffset)); + const highF = 78 + (dayOffset % 3); + const lowF = 55 + (dayOffset % 2); + return { + date: date.toISOString().slice(0, 10), + date_epoch: Math.floor(date.getTime() / 1000), + day: { + maxtemp_c: Math.round(((highF - 32) * 5) / 9), + maxtemp_f: highF, + mintemp_c: Math.round(((lowF - 32) * 5) / 9), + mintemp_f: lowF, + avgtemp_c: Math.round((((highF + lowF) / 2 - 32) * 5) / 9), + avgtemp_f: (highF + lowF) / 2, + maxwind_mph: 18, + maxwind_kph: 29, + totalprecip_mm: dayOffset === 2 ? 1.2 : 0, + totalprecip_in: dayOffset === 2 ? 0.05 : 0, + totalsnow_cm: 0, + avghumidity: 35, + avgvis_km: 16, + avgvis_miles: 10, + uv: 5, + condition: stubCondition, + daily_chance_of_rain: dayOffset === 2 ? 30 : 5, + daily_chance_of_snow: 0, + }, + astro: { + sunrise: '05:48 AM', + sunset: '08:27 PM', + moonrise: '10:15 PM', + moonset: '07:12 AM', + moon_phase: 'Waxing Crescent', + moon_illumination: 18, + }, + hour: [], + }; +}; + +type StubWeatherLocation = WeatherAPISearchResponse[number]; + +export const defaultStubLocation: StubWeatherLocation = { + id: 5419384, + name: 'Denver', + region: 'Colorado', + country: 'United States', + lat: 39.74, + lon: -104.98, + url: 'denver-colorado-united-states-of-america', +}; + +const stubLocations: [StubWeatherLocation, ...StubWeatherLocation[]] = [ + defaultStubLocation, + { + id: 5400000, + name: 'Yosemite Valley', + region: 'California', + country: 'United States', + lat: 37.75, + lon: -119.59, + url: 'yosemite-valley-california-united-states-of-america', + }, +] satisfies [StubWeatherLocation, ...StubWeatherLocation[]]; + +export const getStubLocation = (id?: number) => + stubLocations.find((location) => location.id === id) ?? defaultStubLocation; + +export const buildStubForecast = (id?: number): WeatherAPIForecastResponse => { + const location = getStubLocation(id); + return { + location: { + id: location.id, + name: location.name, + region: location.region, + country: location.country, + lat: location.lat, + lon: location.lon, + tz_id: location.name === 'Denver' ? 'America/Denver' : 'America/Los_Angeles', + localtime_epoch: 1784246400, + localtime: '2026-07-16 12:00', + }, + current: buildStubCurrent(location.name === 'Denver' ? 72 : 68), + forecast: { + forecastday: Array.from({ length: 10 }, (_, index) => buildStubForecastDay(index)), + }, + alerts: { alert: [] }, + }; +}; + +export const searchStubLocations = (query: string) => { + const normalizedQuery = query.trim().toLowerCase(); + if (!normalizedQuery) return []; + if (COORDINATE_QUERY_REGEX.test(normalizedQuery)) return [defaultStubLocation]; + return stubLocations.filter((location) => + `${location.name} ${location.region} ${location.country}` + .toLowerCase() + .includes(normalizedQuery), + ); +}; type WeatherData = { location: string; diff --git a/packages/api/src/utils/env-validation.ts b/packages/api/src/utils/env-validation.ts index 7ffa87fce2..264ca212b7 100644 --- a/packages/api/src/utils/env-validation.ts +++ b/packages/api/src/utils/env-validation.ts @@ -220,6 +220,25 @@ const testEnvSchema = apiEnvObjectSchema type ValidatedAppEnv = z.infer; +export function isLocalE2EApiEnv(input: { + databaseUrl: string; + e2eUserId?: string; + openAiApiKey?: string; + requireE2EUser?: boolean; + requireStubOpenAI?: boolean; +}): boolean { + const databaseIsLocal = + input.databaseUrl.includes('127.0.0.1') || input.databaseUrl.includes('localhost'); + if (!databaseIsLocal) return false; + if (input.requireE2EUser === true && !input.e2eUserId) return false; + if (input.requireStubOpenAI === true) { + const isStubOpenAI = + input.openAiApiKey === 'sk-test' || input.openAiApiKey?.startsWith('sk-e2e-stub-') === true; + if (!isStubOpenAI) return false; + } + return true; +} + // Override Cloudflare binding types with proper TypeScript types export type ValidatedEnv = Omit< ValidatedAppEnv, From 3dac6de2d68dbb6d01a3ab4e4b7d1faf22235ae2 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:12:30 -0600 Subject: [PATCH 22/81] =?UTF-8?q?=F0=9F=90=87=20fix:=20handle=20Swift=20ru?= =?UTF-8?q?nner=20spawn=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/scripts/lib/e2e-api.ts | 7 +++++++ apps/swift/scripts/run-e2e-macos.ts | 3 ++- apps/swift/scripts/run-e2e.ts | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/swift/scripts/lib/e2e-api.ts b/apps/swift/scripts/lib/e2e-api.ts index 884d7ecbbc..a118d133a0 100644 --- a/apps/swift/scripts/lib/e2e-api.ts +++ b/apps/swift/scripts/lib/e2e-api.ts @@ -31,12 +31,19 @@ async function waitForHealthy(input: { baseURL: string; child: ChildProcess }): const startedAt = Date.now(); let exited = false; let exitCode: number | null = null; + let spawnError: Error | undefined; + child.once('error', (error) => { + spawnError = error; + }); child.once('exit', (code) => { exited = true; exitCode = code; }); while (Date.now() - startedAt < 120_000) { + if (spawnError) { + throw new Error(`Local E2E API failed to start: ${spawnError.message}`); + } if (await isHealthy(baseURL)) return; if (exited) { throw new Error( diff --git a/apps/swift/scripts/run-e2e-macos.ts b/apps/swift/scripts/run-e2e-macos.ts index de15de2834..0ba41dfffc 100644 --- a/apps/swift/scripts/run-e2e-macos.ts +++ b/apps/swift/scripts/run-e2e-macos.ts @@ -295,7 +295,7 @@ function redactSecrets(output: string): string { let exitStatus = 1; try { - const resultStatus = await new Promise((resolve) => { + const resultStatus = await new Promise((resolve, reject) => { const child = spawn('xcodebuild', args, { cwd: SWIFT_DIR, env: process.env, @@ -307,6 +307,7 @@ try { child.stderr.on('data', (chunk) => { process.stderr.write(redactSecrets(chunk.toString())); }); + child.once('error', reject); child.on('close', (code) => resolve(code)); }); exitStatus = resultStatus ?? 1; diff --git a/apps/swift/scripts/run-e2e.ts b/apps/swift/scripts/run-e2e.ts index fa82288f2a..ce0eb0ad65 100644 --- a/apps/swift/scripts/run-e2e.ts +++ b/apps/swift/scripts/run-e2e.ts @@ -289,7 +289,7 @@ function redactSecrets(output: string): string { let exitStatus = 1; try { - const resultStatus = await new Promise((resolve) => { + const resultStatus = await new Promise((resolve, reject) => { const child = spawn('xcodebuild', args, { cwd: SWIFT_DIR, env: process.env, @@ -301,6 +301,7 @@ try { child.stderr.on('data', (chunk) => { process.stderr.write(redactSecrets(chunk.toString())); }); + child.once('error', reject); child.on('close', (code) => resolve(code)); }); exitStatus = resultStatus ?? 1; From a346e1e0e1e481959dbf20ff2f2f57752d0e0d82 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:42:31 -0600 Subject: [PATCH 23/81] =?UTF-8?q?=F0=9F=90=9B=20fix:=20await=20trip=20dele?= =?UTF-8?q?te=20navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trips/utils/getTripDetailOptions.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/expo/features/trips/utils/getTripDetailOptions.tsx b/apps/expo/features/trips/utils/getTripDetailOptions.tsx index f8b965827c..06b8e13c0a 100644 --- a/apps/expo/features/trips/utils/getTripDetailOptions.tsx +++ b/apps/expo/features/trips/utils/getTripDetailOptions.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'expo-app/lib/hooks/useTranslation'; import { t } from 'expo-app/lib/i18n'; import { testIds } from 'expo-app/lib/testIds'; import { useRouter } from 'expo-router'; +import { useState } from 'react'; import { Platform, Share, View } from 'react-native'; import { useDeleteTrip } from '../hooks'; @@ -19,6 +20,7 @@ export function getTripDetailOptions(id: string) { const router = useRouter(); const deleteTrip = useDeleteTrip(); const trip = useTripDetailsFromStore(id); + const [isDeleting, setIsDeleting] = useState(false); const handleShare = async () => { if (!trip) return; @@ -36,15 +38,25 @@ export function getTripDetailOptions(id: string) { } }; - const deleteAndNavigate = () => { - deleteTrip(id); - if (router.canGoBack()) router.back(); + const deleteAndNavigate = async () => { + if (isDeleting) return; + setIsDeleting(true); + try { + await deleteTrip(id); + if (router.canGoBack()) { + router.back(); + } else { + router.replace('/trips'); + } + } finally { + setIsDeleting(false); + } }; const confirmDelete = () => { if (Platform.OS === 'web') { if (globalThis.confirm(t('trips.deleteTripConfirmation'))) { - deleteAndNavigate(); + void deleteAndNavigate(); } return; } @@ -57,7 +69,9 @@ export function getTripDetailOptions(id: string) { { text: t('common.delete'), style: 'destructive', - onPress: deleteAndNavigate, + onPress: () => { + void deleteAndNavigate(); + }, }, ], }); @@ -77,6 +91,7 @@ export function getTripDetailOptions(id: string) { testID={testIds.trips.deleteBtn} variant="plain" size="icon" + disabled={isDeleting} onPress={confirmDelete} > From d95fa3331626e5f7428802e9b6001526805bca13 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 01:10:52 -0600 Subject: [PATCH 24/81] =?UTF-8?q?=F0=9F=90=87=20fix:=20give=20iOS=20smoke?= =?UTF-8?q?=20CI=20headroom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index c18499472a..f2d50fe4d3 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -27,7 +27,7 @@ jobs: build-and-smoke: name: ${{ matrix.scheme }} (smoke) runs-on: macos-15 - timeout-minutes: 30 + timeout-minutes: 45 strategy: fail-fast: false matrix: @@ -74,7 +74,7 @@ jobs: echo "IOS_SIMULATOR_ID=$DEVICE_ID" >> "$GITHUB_ENV" - name: Run ${{ matrix.plan }} test plan - timeout-minutes: 20 + timeout-minutes: 35 env: E2E_EMAIL: ${{ secrets.E2E_EMAIL }} E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }} From 8b643a58d014845f230b88dbf2eeba35172a8d3b Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 01:41:36 -0600 Subject: [PATCH 25/81] =?UTF-8?q?=F0=9F=A7=AA=20test:=20guard=20Swift=20Te?= =?UTF-8?q?stFlight=20lanes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/README.md | 22 ++++ apps/swift/Resources/Info-iOS.plist | 2 +- apps/swift/Resources/Info-macOS.plist | 2 +- apps/swift/Resources/Info-watchOS.plist | 2 +- apps/swift/project.yml | 9 +- .../__tests__/testflight-config.test.ts | 85 +++++++++++++++ apps/swift/scripts/lib/testflight-config.ts | 67 ++++++++++++ apps/swift/scripts/upload-testflight.ts | 101 ++++++++++++------ apps/swift/xcconfig/Config-Debug.xcconfig | 1 + apps/swift/xcconfig/Config-Release.xcconfig | 1 + apps/swift/xcconfig/Config-Staging.xcconfig | 1 + packages/env/src/node.ts | 8 ++ 12 files changed, 260 insertions(+), 41 deletions(-) create mode 100644 apps/swift/scripts/__tests__/testflight-config.test.ts create mode 100644 apps/swift/scripts/lib/testflight-config.ts diff --git a/apps/swift/README.md b/apps/swift/README.md index d6ba1bd690..3969b2bc85 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -71,6 +71,28 @@ Swift E2E CI is defined in `.github/workflows/swift-e2e.yml`. See `docs/ci/swift-e2e-runner.md` for self-hosted Mac runner setup. +## TestFlight Lanes + +The Swift iOS app has two TestFlight identities: + +- Replacement beta: `com.andrewbierman.packrat`, display name `PackRat`. This is + the existing Expo/App Store listing and is the only lane that can validate a + seamless update for existing testers. +- Side-by-side beta: `com.andrewbierman.packrat.swift`, display name + `PackRat Swift`. This is useful for parallel Swift QA, but iOS treats it as a + separate app with separate install, keychain, and app container state. + +Upload commands require an explicit lane so we do not accidentally test the +wrong App Store Connect record: + +```sh +bun apps/swift/scripts/upload-testflight.ts --replacement +bun apps/swift/scripts/upload-testflight.ts --side-by-side --staging +``` + +`--staging` uses the Staging build config (`PACKRAT_ENV=dev`). Without it, the +script archives Release (`PACKRAT_ENV=production`). + ## Data Isolation Swift E2E tests use unique names for records they create. That keeps repeated diff --git a/apps/swift/Resources/Info-iOS.plist b/apps/swift/Resources/Info-iOS.plist index 3bdf4210c9..254785a61f 100644 --- a/apps/swift/Resources/Info-iOS.plist +++ b/apps/swift/Resources/Info-iOS.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - PackRat Swift + $(PACKRAT_DISPLAY_NAME) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconName diff --git a/apps/swift/Resources/Info-macOS.plist b/apps/swift/Resources/Info-macOS.plist index ec5527b16b..09dbc4e89f 100644 --- a/apps/swift/Resources/Info-macOS.plist +++ b/apps/swift/Resources/Info-macOS.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - PackRat Swift + $(PACKRAT_DISPLAY_NAME) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconName diff --git a/apps/swift/Resources/Info-watchOS.plist b/apps/swift/Resources/Info-watchOS.plist index 3e208bde63..bd741ae16b 100644 --- a/apps/swift/Resources/Info-watchOS.plist +++ b/apps/swift/Resources/Info-watchOS.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - PackRat Swift + $(PACKRAT_DISPLAY_NAME) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconName diff --git a/apps/swift/project.yml b/apps/swift/project.yml index 94ea799fe4..831e3d6773 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -69,7 +69,7 @@ targets: info: path: Resources/Info-iOS.plist properties: - CFBundleDisplayName: PackRat Swift + CFBundleDisplayName: $(PACKRAT_DISPLAY_NAME) CFBundleIconName: AppIcon CFBundleShortVersionString: "2.0.29" CFBundleVersion: "2026071601" @@ -142,6 +142,7 @@ targets: CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift + PACKRAT_DISPLAY_NAME: PackRat Swift PRODUCT_MODULE_NAME: PackRat TARGETED_DEVICE_FAMILY: "1,2" ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon @@ -160,7 +161,7 @@ targets: info: path: Resources/Info-watchOS.plist properties: - CFBundleDisplayName: PackRat Swift + CFBundleDisplayName: $(PACKRAT_DISPLAY_NAME) CFBundleIconName: WatchAppIcon CFBundleShortVersionString: "2.0.29" CFBundleVersion: "2026071601" @@ -175,6 +176,7 @@ targets: CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift.watchkitapp + PACKRAT_DISPLAY_NAME: PackRat Swift PRODUCT_MODULE_NAME: PackRatWatch ASSETCATALOG_COMPILER_APPICON_NAME: WatchAppIcon @@ -195,7 +197,7 @@ targets: info: path: Resources/Info-macOS.plist properties: - CFBundleDisplayName: PackRat Swift + CFBundleDisplayName: $(PACKRAT_DISPLAY_NAME) CFBundleIconName: AppIcon CFBundleShortVersionString: "2.0.29" CFBundleVersion: "2026071601" @@ -234,6 +236,7 @@ targets: CODE_SIGN_IDENTITY: "-" CODE_SIGNING_REQUIRED: NO PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.mac + PACKRAT_DISPLAY_NAME: PackRat Swift # Match iOS target so @testable import PackRat resolves on both platforms. PRODUCT_MODULE_NAME: PackRat ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon diff --git a/apps/swift/scripts/__tests__/testflight-config.test.ts b/apps/swift/scripts/__tests__/testflight-config.test.ts new file mode 100644 index 0000000000..39507a281c --- /dev/null +++ b/apps/swift/scripts/__tests__/testflight-config.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest'; +import { + parseTestFlightUploadConfig, + TestFlightConfigError, + xcodeArchiveOverrides, +} from '../lib/testflight-config'; + +describe('parseTestFlightUploadConfig', () => { + it('requires an explicit TestFlight lane', () => { + expect(() => parseTestFlightUploadConfig({ argv: [], env: { BUILD_NUMBER: '123' } })).toThrow( + TestFlightConfigError, + ); + }); + + it('rejects conflicting lanes', () => { + expect(() => + parseTestFlightUploadConfig({ + argv: ['--side-by-side', '--replacement'], + env: { BUILD_NUMBER: '123' }, + }), + ).toThrow('Choose exactly one TestFlight lane'); + }); + + it('builds the side-by-side Swift beta identity', () => { + expect( + parseTestFlightUploadConfig({ + argv: ['--side-by-side'], + env: { BUILD_NUMBER: '123' }, + }), + ).toMatchObject({ + lane: 'side-by-side', + staging: false, + scheme: 'PackRat-iOS', + configuration: 'Release', + bundleId: 'com.andrewbierman.packrat.swift', + displayName: 'PackRat Swift', + buildNumber: '123', + }); + }); + + it('builds the replacement identity for the existing Expo listing', () => { + const config = parseTestFlightUploadConfig({ + argv: ['--replacement'], + env: { BUILD_NUMBER: '456' }, + }); + + expect(config).toMatchObject({ + lane: 'replacement', + staging: false, + scheme: 'PackRat-iOS', + configuration: 'Release', + bundleId: 'com.andrewbierman.packrat', + displayName: 'PackRat', + buildNumber: '456', + }); + expect(xcodeArchiveOverrides({ config, teamId: 'TEAM123' })).toEqual([ + 'CURRENT_PROJECT_VERSION=456', + 'DEVELOPMENT_TEAM=TEAM123', + 'PRODUCT_BUNDLE_IDENTIFIER=com.andrewbierman.packrat', + 'PACKRAT_DISPLAY_NAME=PackRat', + ]); + }); + + it('uses the staging scheme without changing the selected lane identity', () => { + expect( + parseTestFlightUploadConfig({ + argv: ['--replacement', '--staging'], + env: { BUILD_NUMBER: '789' }, + }), + ).toMatchObject({ + lane: 'replacement', + staging: true, + scheme: 'PackRat-iOS-Staging', + configuration: 'Staging', + bundleId: 'com.andrewbierman.packrat', + displayName: 'PackRat', + }); + }); + + it('rejects conflicting API profile flags', () => { + expect(() => + parseTestFlightUploadConfig({ argv: ['--replacement', '--staging', '--production'] }), + ).toThrow('Use either --staging or --production, not both.'); + }); +}); diff --git a/apps/swift/scripts/lib/testflight-config.ts b/apps/swift/scripts/lib/testflight-config.ts new file mode 100644 index 0000000000..5fda2332bb --- /dev/null +++ b/apps/swift/scripts/lib/testflight-config.ts @@ -0,0 +1,67 @@ +export type TestFlightLane = 'side-by-side' | 'replacement'; + +export type TestFlightUploadConfig = { + lane: TestFlightLane; + staging: boolean; + scheme: string; + configuration: string; + bundleId: string; + displayName: string; + buildNumber: string; +}; + +export class TestFlightConfigError extends Error { + constructor(message: string) { + super(message); + this.name = 'TestFlightConfigError'; + } +} + +const SIDE_BY_SIDE_BUNDLE_ID = 'com.andrewbierman.packrat.swift'; +const REPLACEMENT_BUNDLE_ID = 'com.andrewbierman.packrat'; + +export function parseTestFlightUploadConfig(input: { + argv: readonly string[]; + env?: { BUILD_NUMBER?: string | undefined }; +}): TestFlightUploadConfig { + const { argv, env = {} } = input; + const sideBySide = argv.includes('--side-by-side'); + const replacement = argv.includes('--replacement'); + const staging = argv.includes('--staging'); + const production = argv.includes('--production'); + + if (sideBySide === replacement) { + throw new TestFlightConfigError( + 'Choose exactly one TestFlight lane: --replacement for the existing Expo/App Store listing, or --side-by-side for the separate Swift beta app.', + ); + } + if (staging && production) { + throw new TestFlightConfigError('Use either --staging or --production, not both.'); + } + + const lane: TestFlightLane = replacement ? 'replacement' : 'side-by-side'; + const buildNumber = env.BUILD_NUMBER ?? String(Math.floor(Date.now() / 1000)); + + return { + lane, + staging, + scheme: staging ? 'PackRat-iOS-Staging' : 'PackRat-iOS', + configuration: staging ? 'Staging' : 'Release', + bundleId: replacement ? REPLACEMENT_BUNDLE_ID : SIDE_BY_SIDE_BUNDLE_ID, + displayName: replacement ? 'PackRat' : 'PackRat Swift', + buildNumber, + }; +} + +export function xcodeArchiveOverrides(input: { + config: TestFlightUploadConfig; + teamId: string; +}): string[] { + const { config, teamId } = input; + return [ + `CURRENT_PROJECT_VERSION=${config.buildNumber}`, + `DEVELOPMENT_TEAM=${teamId}`, + `PRODUCT_BUNDLE_IDENTIFIER=${config.bundleId}`, + `PACKRAT_DISPLAY_NAME=${config.displayName}`, + ]; +} diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index 038ee7ecd9..be302af097 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -2,9 +2,11 @@ /** * Archive the native Swift PackRat iOS app and upload it to TestFlight. * - * This targets a SEPARATE App Store Connect record from the production Expo - * app: bundle id `com.andrewbierman.packrat.swift`. Register that app record - * in App Store Connect once before the first upload. + * Choose the App Store Connect lane explicitly: + * --replacement existing Expo/App Store listing (`com.andrewbierman.packrat`, + * display name `PackRat`) for true TestFlight update testing. + * --side-by-side separate Swift beta listing (`com.andrewbierman.packrat.swift`, + * display name `PackRat Swift`) for parallel beta installs. * * Auth uses an Apple ID + app-specific password (no App Store Connect API key * required). Generate a password at appleid.apple.com -> Sign-In & Security -> @@ -19,50 +21,80 @@ * BUILD_NUMBER CFBundleVersion for this upload (default: timestamp) * * Flags: + * --replacement Archive for the existing Expo/App Store iOS listing. + * --side-by-side Archive for the separate Swift beta listing. * --staging Archive the Staging config (PACKRAT_ENV=dev) so the - * TestFlight build targets the deployed DEV API instead - * of production. Default (no flag) = Release/production. + * build targets the deployed DEV API instead of production. + * --production Optional clarity flag; Release/production is the default + * API profile when --staging is absent. * * Usage: - * bun apps/swift/scripts/upload-testflight.ts # production - * bun apps/swift/scripts/upload-testflight.ts --staging # dev API + * bun apps/swift/scripts/upload-testflight.ts --replacement + * bun apps/swift/scripts/upload-testflight.ts --side-by-side --staging */ import { execFileSync } from 'node:child_process'; import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { nodeEnv } from '@packrat/env/node'; +import { + parseTestFlightUploadConfig, + TestFlightConfigError, + type TestFlightUploadConfig, + xcodeArchiveOverrides, +} from './lib/testflight-config'; const SWIFT_DIR = new URL('..', import.meta.url).pathname; const PROJECT = join(SWIFT_DIR, 'PackRat.xcodeproj'); -const BUNDLE_ID = 'com.andrewbierman.packrat.swift'; +const HELP = process.argv.includes('--help') || process.argv.includes('-h'); -// --staging archives the Staging config (PACKRAT_ENV=dev) via the dedicated -// scheme; the default archives PackRat-iOS (Release config → production). -const STAGING = process.argv.includes('--staging'); -const SCHEME = STAGING ? 'PackRat-iOS-Staging' : 'PackRat-iOS'; -const CONFIGURATION = STAGING ? 'Staging' : 'Release'; +function usage(): string { + return [ + 'Usage:', + ' bun apps/swift/scripts/upload-testflight.ts --replacement [--production|--staging]', + ' bun apps/swift/scripts/upload-testflight.ts --side-by-side [--production|--staging]', + '', + 'Lanes:', + ' --replacement Existing Expo/App Store listing: com.andrewbierman.packrat, PackRat.', + ' --side-by-side Separate Swift beta listing: com.andrewbierman.packrat.swift, PackRat Swift.', + ].join('\n'); +} + +if (HELP) { + console.log(usage()); + process.exit(0); +} + +let uploadConfig: TestFlightUploadConfig; +try { + uploadConfig = parseTestFlightUploadConfig({ + argv: process.argv.slice(2), + }); +} catch (error) { + if (error instanceof TestFlightConfigError) { + console.error(`${error.message}\n\n${usage()}`); + process.exit(1); + } + throw error; +} + +const { nodeEnv } = await import('@packrat/env/node'); -function req(name: string): string { - const v = - name === 'APPLE_ID' - ? nodeEnv.APPLE_ID - : name === 'APPLE_APP_PASSWORD' - ? nodeEnv.APPLE_APP_PASSWORD - : name === 'APPLE_TEAM_ID' - ? nodeEnv.APPLE_TEAM_ID - : undefined; +function req(input: { name: 'APPLE_ID' | 'APPLE_APP_PASSWORD' | 'APPLE_TEAM_ID' }): string { + const v = nodeEnv[input.name]; if (!v) { - console.error(`Missing required env var: ${name}. See script header.`); + console.error(`Missing required env var: ${input.name}. See script header.`); process.exit(1); } return v; } -const appleId = req('APPLE_ID'); -const appPassword = req('APPLE_APP_PASSWORD'); -const teamId = req('APPLE_TEAM_ID'); -const buildNumber = nodeEnv.BUILD_NUMBER ?? String(Math.floor(Date.now() / 1000)); +if (nodeEnv.BUILD_NUMBER) { + uploadConfig = { ...uploadConfig, buildNumber: nodeEnv.BUILD_NUMBER }; +} + +const appleId = req({ name: 'APPLE_ID' }); +const appPassword = req({ name: 'APPLE_APP_PASSWORD' }); +const teamId = req({ name: 'APPLE_TEAM_ID' }); const work = mkdtempSync(join(tmpdir(), 'packrat-tf-')); const archivePath = join(work, 'PackRat.xcarchive'); @@ -82,9 +114,9 @@ run({ '-project', PROJECT, '-scheme', - SCHEME, + uploadConfig.scheme, '-configuration', - CONFIGURATION, + uploadConfig.configuration, '-destination', 'generic/platform=iOS', '-archivePath', @@ -92,8 +124,7 @@ run({ // Lets Xcode register the App IDs and generate provisioning profiles for // the (new) bundle ids on the fly, using the signed-in account. '-allowProvisioningUpdates', - `CURRENT_PROJECT_VERSION=${buildNumber}`, - `DEVELOPMENT_TEAM=${teamId}`, + ...xcodeArchiveOverrides({ config: uploadConfig, teamId }), ], }); @@ -134,7 +165,7 @@ run({ // 3. Upload to TestFlight via altool (app-specific-password auth). // `--asc-provider` (team short name) is required when the Apple ID belongs to // more than one team, so altool knows which one to deliver to. -const ipa = join(exportDir, `${SCHEME}.ipa`); +const ipa = join(exportDir, `${uploadConfig.scheme}.ipa`); run({ cmd: 'xcrun', args: [ @@ -154,7 +185,7 @@ run({ }); console.log( - `\n✓ Uploaded build ${buildNumber} to TestFlight (${BUNDLE_ID}, ${CONFIGURATION}` + - `${STAGING ? ' → dev API' : ' → production'}).`, + `\n✓ Uploaded build ${uploadConfig.buildNumber} to TestFlight (${uploadConfig.bundleId}, ${uploadConfig.displayName}, ${uploadConfig.configuration}` + + `${uploadConfig.staging ? ' -> dev API' : ' -> production'}, ${uploadConfig.lane}).`, ); console.log('It will appear in App Store Connect after processing (usually 5-15 min).'); diff --git a/apps/swift/xcconfig/Config-Debug.xcconfig b/apps/swift/xcconfig/Config-Debug.xcconfig index b3e6e8cb81..6192b15f56 100644 --- a/apps/swift/xcconfig/Config-Debug.xcconfig +++ b/apps/swift/xcconfig/Config-Debug.xcconfig @@ -19,6 +19,7 @@ // echo "PACKRAT_ENV = dev" > xcconfig/Config-Debug.local.xcconfig # deployed dev // echo "PACKRAT_ENV = dev-local" > xcconfig/Config-Debug.local.xcconfig # wrangler on :8791 PACKRAT_ENV = local +PACKRAT_DISPLAY_NAME = PackRat Swift // SENTRY_DSN flows xcconfig → Info.plist → runtime. // Drop the DSN into Config-Debug.local.xcconfig to enable Sentry on Debug diff --git a/apps/swift/xcconfig/Config-Release.xcconfig b/apps/swift/xcconfig/Config-Release.xcconfig index 5be651e2c3..c624213796 100644 --- a/apps/swift/xcconfig/Config-Release.xcconfig +++ b/apps/swift/xcconfig/Config-Release.xcconfig @@ -1,4 +1,5 @@ PACKRAT_ENV = production +PACKRAT_DISPLAY_NAME = PackRat Swift // SENTRY_DSN flows xcconfig → Info.plist → runtime. Set this in the build // environment (CI secret, Release-build script, or Config-Release.local.xcconfig diff --git a/apps/swift/xcconfig/Config-Staging.xcconfig b/apps/swift/xcconfig/Config-Staging.xcconfig index 70ea560e26..3ea7816811 100644 --- a/apps/swift/xcconfig/Config-Staging.xcconfig +++ b/apps/swift/xcconfig/Config-Staging.xcconfig @@ -11,6 +11,7 @@ // xcconfig files treat // as a comment, so full URLs can't be stored here. // PACKRAT_ENV selects an environment name; URLs live in APIClient.swift. PACKRAT_ENV = dev +PACKRAT_DISPLAY_NAME = PackRat Swift // SENTRY_DSN flows xcconfig → Info.plist → runtime. Provided by CI from a // secret at archive time; empty disables Sentry (SDK silently no-ops). diff --git a/packages/env/src/node.ts b/packages/env/src/node.ts index c13efba9e5..4055fa820d 100644 --- a/packages/env/src/node.ts +++ b/packages/env/src/node.ts @@ -117,6 +117,10 @@ export const nodeEnvSchema = z.object({ PACKRAT_WATCH_SYNC_WAIT_MS: z.string().regex(/^\d+$/).optional(), PACKRAT_WATCH_SYNC_PHONE_ID: z.string().min(1).optional(), PACKRAT_WATCH_SYNC_WATCH_ID: z.string().min(1).optional(), + APPLE_ID: z.string().email().optional(), + APPLE_APP_PASSWORD: z.string().min(1).optional(), + APPLE_TEAM_ID: z.string().min(1).optional(), + BUILD_NUMBER: z.string().regex(/^\d+$/).optional(), }); export type NodeEnv = z.infer; @@ -191,4 +195,8 @@ export const nodeEnv = nodeEnvSchema.parse({ PACKRAT_WATCH_SYNC_WAIT_MS: process.env.PACKRAT_WATCH_SYNC_WAIT_MS, PACKRAT_WATCH_SYNC_PHONE_ID: process.env.PACKRAT_WATCH_SYNC_PHONE_ID, PACKRAT_WATCH_SYNC_WATCH_ID: process.env.PACKRAT_WATCH_SYNC_WATCH_ID, + APPLE_ID: process.env.APPLE_ID, + APPLE_APP_PASSWORD: process.env.APPLE_APP_PASSWORD, + APPLE_TEAM_ID: process.env.APPLE_TEAM_ID, + BUILD_NUMBER: process.env.BUILD_NUMBER, }); From 364e32b02599568e40993ac087d511f5af0e8b27 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:28:21 -0600 Subject: [PATCH 26/81] =?UTF-8?q?=F0=9F=A7=AA=20test:=20add=20Swift=20Test?= =?UTF-8?q?Flight=20dry=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/README.md | 5 +++ .../__tests__/testflight-config.test.ts | 22 +++++++++++++ apps/swift/scripts/lib/testflight-config.ts | 5 +++ apps/swift/scripts/upload-testflight.ts | 31 +++++++++++++++++-- 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/apps/swift/README.md b/apps/swift/README.md index 3969b2bc85..45a9a36f62 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -86,6 +86,7 @@ Upload commands require an explicit lane so we do not accidentally test the wrong App Store Connect record: ```sh +bun apps/swift/scripts/upload-testflight.ts --replacement --dry-run bun apps/swift/scripts/upload-testflight.ts --replacement bun apps/swift/scripts/upload-testflight.ts --side-by-side --staging ``` @@ -93,6 +94,10 @@ bun apps/swift/scripts/upload-testflight.ts --side-by-side --staging `--staging` uses the Staging build config (`PACKRAT_ENV=dev`). Without it, the script archives Release (`PACKRAT_ENV=production`). +Use `--dry-run` before a real upload to verify the lane, bundle id, display +name, build configuration, API environment, and Xcode archive overrides without +requiring Apple credentials or running Xcode. + ## Data Isolation Swift E2E tests use unique names for records they create. That keeps repeated diff --git a/apps/swift/scripts/__tests__/testflight-config.test.ts b/apps/swift/scripts/__tests__/testflight-config.test.ts index 39507a281c..17531a813e 100644 --- a/apps/swift/scripts/__tests__/testflight-config.test.ts +++ b/apps/swift/scripts/__tests__/testflight-config.test.ts @@ -30,11 +30,13 @@ describe('parseTestFlightUploadConfig', () => { ).toMatchObject({ lane: 'side-by-side', staging: false, + dryRun: false, scheme: 'PackRat-iOS', configuration: 'Release', bundleId: 'com.andrewbierman.packrat.swift', displayName: 'PackRat Swift', buildNumber: '123', + apiEnvironment: 'production', }); }); @@ -47,11 +49,13 @@ describe('parseTestFlightUploadConfig', () => { expect(config).toMatchObject({ lane: 'replacement', staging: false, + dryRun: false, scheme: 'PackRat-iOS', configuration: 'Release', bundleId: 'com.andrewbierman.packrat', displayName: 'PackRat', buildNumber: '456', + apiEnvironment: 'production', }); expect(xcodeArchiveOverrides({ config, teamId: 'TEAM123' })).toEqual([ 'CURRENT_PROJECT_VERSION=456', @@ -70,10 +74,28 @@ describe('parseTestFlightUploadConfig', () => { ).toMatchObject({ lane: 'replacement', staging: true, + dryRun: false, scheme: 'PackRat-iOS-Staging', configuration: 'Staging', bundleId: 'com.andrewbierman.packrat', displayName: 'PackRat', + apiEnvironment: 'dev', + }); + }); + + it('supports dry-run preflight without changing identity', () => { + expect( + parseTestFlightUploadConfig({ + argv: ['--replacement', '--dry-run'], + env: { BUILD_NUMBER: '101' }, + }), + ).toMatchObject({ + lane: 'replacement', + dryRun: true, + bundleId: 'com.andrewbierman.packrat', + displayName: 'PackRat', + apiEnvironment: 'production', + buildNumber: '101', }); }); diff --git a/apps/swift/scripts/lib/testflight-config.ts b/apps/swift/scripts/lib/testflight-config.ts index 5fda2332bb..3cf23731b2 100644 --- a/apps/swift/scripts/lib/testflight-config.ts +++ b/apps/swift/scripts/lib/testflight-config.ts @@ -3,11 +3,13 @@ export type TestFlightLane = 'side-by-side' | 'replacement'; export type TestFlightUploadConfig = { lane: TestFlightLane; staging: boolean; + dryRun: boolean; scheme: string; configuration: string; bundleId: string; displayName: string; buildNumber: string; + apiEnvironment: 'dev' | 'production'; }; export class TestFlightConfigError extends Error { @@ -29,6 +31,7 @@ export function parseTestFlightUploadConfig(input: { const replacement = argv.includes('--replacement'); const staging = argv.includes('--staging'); const production = argv.includes('--production'); + const dryRun = argv.includes('--dry-run'); if (sideBySide === replacement) { throw new TestFlightConfigError( @@ -45,11 +48,13 @@ export function parseTestFlightUploadConfig(input: { return { lane, staging, + dryRun, scheme: staging ? 'PackRat-iOS-Staging' : 'PackRat-iOS', configuration: staging ? 'Staging' : 'Release', bundleId: replacement ? REPLACEMENT_BUNDLE_ID : SIDE_BY_SIDE_BUNDLE_ID, displayName: replacement ? 'PackRat' : 'PackRat Swift', buildNumber, + apiEnvironment: staging ? 'dev' : 'production', }; } diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index be302af097..41b6b3f579 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -27,15 +27,19 @@ * build targets the deployed DEV API instead of production. * --production Optional clarity flag; Release/production is the default * API profile when --staging is absent. + * --dry-run Print the resolved archive identity/settings and exit + * before reading Apple credentials or running Xcode. * * Usage: * bun apps/swift/scripts/upload-testflight.ts --replacement + * bun apps/swift/scripts/upload-testflight.ts --replacement --dry-run * bun apps/swift/scripts/upload-testflight.ts --side-by-side --staging */ import { execFileSync } from 'node:child_process'; import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { safeJsonStringify } from '@packrat/utils'; import { parseTestFlightUploadConfig, TestFlightConfigError, @@ -50,8 +54,8 @@ const HELP = process.argv.includes('--help') || process.argv.includes('-h'); function usage(): string { return [ 'Usage:', - ' bun apps/swift/scripts/upload-testflight.ts --replacement [--production|--staging]', - ' bun apps/swift/scripts/upload-testflight.ts --side-by-side [--production|--staging]', + ' bun apps/swift/scripts/upload-testflight.ts --replacement [--production|--staging] [--dry-run]', + ' bun apps/swift/scripts/upload-testflight.ts --side-by-side [--production|--staging] [--dry-run]', '', 'Lanes:', ' --replacement Existing Expo/App Store listing: com.andrewbierman.packrat, PackRat.', @@ -77,6 +81,28 @@ try { throw error; } +function printPreflight(input: { config: TestFlightUploadConfig; teamId?: string }) { + const { config, teamId = '' } = input; + const archiveOverrides = xcodeArchiveOverrides({ config, teamId }); + console.log( + safeJsonStringify({ + lane: config.lane, + bundleId: config.bundleId, + displayName: config.displayName, + scheme: config.scheme, + configuration: config.configuration, + apiEnvironment: config.apiEnvironment, + buildNumber: config.buildNumber, + archiveOverrides, + }), + ); +} + +if (uploadConfig.dryRun) { + printPreflight({ config: uploadConfig }); + process.exit(0); +} + const { nodeEnv } = await import('@packrat/env/node'); function req(input: { name: 'APPLE_ID' | 'APPLE_APP_PASSWORD' | 'APPLE_TEAM_ID' }): string { @@ -95,6 +121,7 @@ if (nodeEnv.BUILD_NUMBER) { const appleId = req({ name: 'APPLE_ID' }); const appPassword = req({ name: 'APPLE_APP_PASSWORD' }); const teamId = req({ name: 'APPLE_TEAM_ID' }); +printPreflight({ config: uploadConfig, teamId }); const work = mkdtempSync(join(tmpdir(), 'packrat-tf-')); const archivePath = join(work, 'PackRat.xcarchive'); From 1a2049f214d93b1bc6129baa57f97862b6f25441 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:49:04 -0600 Subject: [PATCH 27/81] =?UTF-8?q?=F0=9F=90=9B=20fix:=20locate=20exported?= =?UTF-8?q?=20TestFlight=20ipa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/testflight-export.test.ts | 45 +++++++++++++++++++ apps/swift/scripts/lib/testflight-export.ts | 26 +++++++++++ apps/swift/scripts/upload-testflight.ts | 3 +- 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 apps/swift/scripts/__tests__/testflight-export.test.ts create mode 100644 apps/swift/scripts/lib/testflight-export.ts diff --git a/apps/swift/scripts/__tests__/testflight-export.test.ts b/apps/swift/scripts/__tests__/testflight-export.test.ts new file mode 100644 index 0000000000..062dc54dc5 --- /dev/null +++ b/apps/swift/scripts/__tests__/testflight-export.test.ts @@ -0,0 +1,45 @@ +import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { basename, join } from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; +import { findExportedIPA, TestFlightExportError } from '../lib/testflight-export'; + +let tempDirs: string[] = []; + +function makeTempDir() { + const dir = mkdtempSync(join(tmpdir(), 'packrat-testflight-export-')); + tempDirs.push(dir); + return dir; +} + +afterEach(() => { + for (const dir of tempDirs) { + rmSync(dir, { recursive: true, force: true }); + } + tempDirs = []; +}); + +describe('findExportedIPA', () => { + it('returns the exported ipa regardless of scheme name', () => { + const dir = makeTempDir(); + writeFileSync(join(dir, 'PackRat.ipa'), ''); + + expect(basename(findExportedIPA(dir))).toBe('PackRat.ipa'); + }); + + it('fails when export did not produce an ipa', () => { + const dir = makeTempDir(); + writeFileSync(join(dir, 'ExportOptions.plist'), ''); + + expect(() => findExportedIPA(dir)).toThrow(TestFlightExportError); + expect(() => findExportedIPA(dir)).toThrow('No .ipa file found'); + }); + + it('fails when export produced multiple ipa files', () => { + const dir = makeTempDir(); + writeFileSync(join(dir, 'PackRat.ipa'), ''); + writeFileSync(join(dir, 'PackRat Swift.ipa'), ''); + + expect(() => findExportedIPA(dir)).toThrow('Expected one .ipa file'); + }); +}); diff --git a/apps/swift/scripts/lib/testflight-export.ts b/apps/swift/scripts/lib/testflight-export.ts new file mode 100644 index 0000000000..0940957419 --- /dev/null +++ b/apps/swift/scripts/lib/testflight-export.ts @@ -0,0 +1,26 @@ +import { readdirSync } from 'node:fs'; +import { join } from 'node:path'; + +export class TestFlightExportError extends Error { + constructor(message: string) { + super(message); + this.name = 'TestFlightExportError'; + } +} + +export function findExportedIPA(exportDir: string): string { + const ipaFiles = readdirSync(exportDir) + .filter((file) => file.endsWith('.ipa')) + .sort(); + + if (ipaFiles.length === 0) { + throw new TestFlightExportError(`No .ipa file found in ${exportDir}.`); + } + if (ipaFiles.length > 1) { + throw new TestFlightExportError( + `Expected one .ipa file in ${exportDir}, found: ${ipaFiles.join(', ')}.`, + ); + } + + return join(exportDir, ipaFiles[0]); +} diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index 41b6b3f579..3aa4102bb9 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -46,6 +46,7 @@ import { type TestFlightUploadConfig, xcodeArchiveOverrides, } from './lib/testflight-config'; +import { findExportedIPA } from './lib/testflight-export'; const SWIFT_DIR = new URL('..', import.meta.url).pathname; const PROJECT = join(SWIFT_DIR, 'PackRat.xcodeproj'); @@ -192,7 +193,7 @@ run({ // 3. Upload to TestFlight via altool (app-specific-password auth). // `--asc-provider` (team short name) is required when the Apple ID belongs to // more than one team, so altool knows which one to deliver to. -const ipa = join(exportDir, `${uploadConfig.scheme}.ipa`); +const ipa = findExportedIPA(exportDir); run({ cmd: 'xcrun', args: [ From cc72f423770096da295878d379a25e9792e3aa6c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 03:33:13 -0600 Subject: [PATCH 28/81] =?UTF-8?q?=F0=9F=90=9B=20fix:=20restore=20Expo=20se?= =?UTF-8?q?ssions=20in=20Swift?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PackRat/Features/Auth/AuthGateView.swift | 8 +- .../Sources/PackRat/Network/AuthManager.swift | 28 +++++ .../PackRat/Network/KeychainService.swift | 119 +++++++++++++++++- .../Tests/PackRatTests/NetworkTests.swift | 43 +++++++ 4 files changed, 196 insertions(+), 2 deletions(-) diff --git a/apps/swift/Sources/PackRat/Features/Auth/AuthGateView.swift b/apps/swift/Sources/PackRat/Features/Auth/AuthGateView.swift index ab27db8fef..80deddd978 100644 --- a/apps/swift/Sources/PackRat/Features/Auth/AuthGateView.swift +++ b/apps/swift/Sources/PackRat/Features/Auth/AuthGateView.swift @@ -9,12 +9,18 @@ struct AuthGateView: View { var body: some View { Group { - if authManager.canUseApp { + if authManager.isRestoringSession { + ProgressView() + .controlSize(.large) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.secondary.opacity(0.08)) + } else if authManager.canUseApp { AppNavigation() } else { authContent } } + .animation(.spring(duration: 0.3), value: authManager.isRestoringSession) .animation(.spring(duration: 0.3), value: authManager.canUseApp) .animation(.spring(duration: 0.3), value: route) .onOpenURL { url in diff --git a/apps/swift/Sources/PackRat/Network/AuthManager.swift b/apps/swift/Sources/PackRat/Network/AuthManager.swift index 337da9b0b6..60aeb1456c 100644 --- a/apps/swift/Sources/PackRat/Network/AuthManager.swift +++ b/apps/swift/Sources/PackRat/Network/AuthManager.swift @@ -10,6 +10,7 @@ import UIKit final class AuthManager { var currentUser: User? var isGuest = false + var isRestoringSession = false var isAuthenticated: Bool { currentUser != nil } var canUseApp: Bool { isAuthenticated || isGuest } @@ -31,6 +32,7 @@ final class AuthManager { return } loadStoredUser() + restoreStoredSessionIfNeeded() } // MARK: - Auth Actions @@ -316,6 +318,32 @@ final class AuthManager { SentryConfig.setUser(id: user.id, email: user.email) } + private func restoreStoredSessionIfNeeded() { + guard currentUser == nil, + !isGuest, + KeychainService.shared.sessionToken != nil + else { + return + } + + isRestoringSession = true + Task { + do { + try await refreshProfile() + } catch PackRatError.unauthorized { + await MainActor.run { + signOut() + } + } catch { + // Preserve the token for transient network failures. The app + // can still recover on the next launch or explicit refresh. + } + await MainActor.run { + isRestoringSession = false + } + } + } + private func seedE2EAuthenticatedUser() { let environment = ProcessInfo.processInfo.environment let email = environment["PACKRAT_E2E_EMAIL"] ?? "e2e@packrat.test" diff --git a/apps/swift/Sources/PackRat/Network/KeychainService.swift b/apps/swift/Sources/PackRat/Network/KeychainService.swift index 454ef9895f..febc1eb89f 100644 --- a/apps/swift/Sources/PackRat/Network/KeychainService.swift +++ b/apps/swift/Sources/PackRat/Network/KeychainService.swift @@ -6,6 +6,12 @@ final class KeychainService: Sendable { private init() {} private let service = "com.andrewbierman.packrat" + private let legacyExpoService = "app" + private let legacyExpoCookieAccount = "packrat_cookie" + private let legacyExpoSessionCookieNames = [ + "better-auth.session_token", + "__Secure-better-auth.session_token", + ] private let userDefaultsPrefix = "e2e_auth_" private var usesUserDefaultsStorage: Bool { ProcessInfo.processInfo.arguments.contains("--use-userdefaults-auth") @@ -19,7 +25,16 @@ final class KeychainService: Sendable { case sessionToken = "session_token" } - var sessionToken: String? { read(.sessionToken) } + var sessionToken: String? { + if let token = read(.sessionToken) { + return token + } + guard let token = readLegacyExpoSessionToken() else { + return nil + } + saveSessionToken(token) + return token + } func saveSessionToken(_ token: String) { save(token, for: .sessionToken) @@ -30,6 +45,7 @@ final class KeychainService: Sendable { /// login screen. func clearTokens() { delete(.sessionToken) + deleteLegacyExpoCookie() } private func save(_ value: String, for key: Key) { @@ -83,4 +99,105 @@ final class KeychainService: Sendable { private func userDefaultsKey(_ key: Key) -> String { "\(userDefaultsPrefix)\(key.rawValue)" } + + private func readLegacyExpoSessionToken() -> String? { + if usesUserDefaultsStorage { + return nil + } + guard let cookieData = readRawKeychainValue( + service: legacyExpoService, + account: legacyExpoCookieAccount, + generic: legacyExpoCookieAccount + ) ?? readRawKeychainValue( + service: legacyExpoService, + account: legacyExpoCookieAccount, + generic: nil + ), + let cookieString = String(data: cookieData, encoding: .utf8), + let data = cookieString.data(using: .utf8), + let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] + else { + return nil + } + + for cookieName in legacyExpoSessionCookieNames { + guard let cookie = object[cookieName] as? [String: Any], + let value = cookie["value"] as? String, + !value.isEmpty + else { + continue + } + return value + } + return nil + } + + private func readRawKeychainValue(service: String, account: String, generic: String?) -> Data? { + var query: [CFString: Any] = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: service, + kSecAttrAccount: account, + kSecReturnData: true, + kSecMatchLimit: kSecMatchLimitOne, + ] + if let generic { + query[kSecAttrGeneric] = generic.data(using: .utf8) + } + var result: AnyObject? + guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess else { + return nil + } + return result as? Data + } + + private func saveRawKeychainValue(_ value: String, service: String, account: String, generic: String?) { + let data = Data(value.utf8) + var query: [CFString: Any] = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: service, + kSecAttrAccount: account, + ] + if let generic { + query[kSecAttrGeneric] = generic.data(using: .utf8) + } + SecItemDelete(query as CFDictionary) + var attributes = query + attributes[kSecValueData] = data + SecItemAdd(attributes as CFDictionary, nil) + } + + private func deleteLegacyExpoCookie() { + if usesUserDefaultsStorage { + return + } + let preciseQuery: [CFString: Any] = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: legacyExpoService, + kSecAttrAccount: legacyExpoCookieAccount, + kSecAttrGeneric: legacyExpoCookieAccount.data(using: .utf8) as Any, + ] + SecItemDelete(preciseQuery as CFDictionary) + + let fallbackQuery: [CFString: Any] = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: legacyExpoService, + kSecAttrAccount: legacyExpoCookieAccount, + ] + SecItemDelete(fallbackQuery as CFDictionary) + } + + #if DEBUG + func saveLegacyExpoCookieForTesting(_ value: String) { + saveRawKeychainValue( + value, + service: legacyExpoService, + account: legacyExpoCookieAccount, + generic: legacyExpoCookieAccount + ) + } + + func clearLegacyExpoCookieForTesting() { + deleteLegacyExpoCookie() + } + #endif } diff --git a/apps/swift/Tests/PackRatTests/NetworkTests.swift b/apps/swift/Tests/PackRatTests/NetworkTests.swift index 8130b0fabf..f3cd4132c0 100644 --- a/apps/swift/Tests/PackRatTests/NetworkTests.swift +++ b/apps/swift/Tests/PackRatTests/NetworkTests.swift @@ -37,6 +37,49 @@ struct KeychainServiceTests { #expect(keychain.sessionToken == "second") keychain.clearTokens() } + + @Test("reads and migrates Expo Better Auth session cookie") + func readsAndMigratesExpoBetterAuthCookie() { + keychain.saveLegacyExpoCookieForTesting(""" + {"better-auth.session_token":{"value":"legacy-session-token"}} + """) + + #expect(keychain.sessionToken == "legacy-session-token") + + keychain.clearLegacyExpoCookieForTesting() + #expect(keychain.sessionToken == "legacy-session-token") + keychain.clearTokens() + } + + @Test("reads Expo secure Better Auth session cookie") + func readsExpoSecureBetterAuthCookie() { + keychain.saveLegacyExpoCookieForTesting(""" + {"__Secure-better-auth.session_token":{"value":"secure-legacy-session-token"}} + """) + + #expect(keychain.sessionToken == "secure-legacy-session-token") + keychain.clearTokens() + } + + @Test("ignores invalid Expo cookie payload") + func ignoresInvalidExpoCookiePayload() { + keychain.saveLegacyExpoCookieForTesting(""" + {"better-auth.session_token":{"value":""}} + """) + + #expect(keychain.sessionToken == nil) + keychain.clearTokens() + } + + @Test("clearTokens removes Expo Better Auth cookie") + func clearTokensRemovesExpoCookie() { + keychain.saveLegacyExpoCookieForTesting(""" + {"better-auth.session_token":{"value":"legacy-session-token"}} + """) + + keychain.clearTokens() + #expect(keychain.sessionToken == nil) + } } // MARK: - APIEndpoint / Endpoint builder From b2c9fb0320ee02ded8d93c51ca294dc262bded56 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 04:01:45 -0600 Subject: [PATCH 29/81] =?UTF-8?q?=F0=9F=93=A6=20add=20Swift=20App=20Store?= =?UTF-8?q?=20metadata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Resources/Info-iOS.plist | 4 + apps/swift/Resources/PackRat-iOS.entitlements | 7 +- apps/swift/Resources/PrivacyInfo.xcprivacy | 76 +++++++++++++++++++ apps/swift/project.yml | 12 ++- 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 apps/swift/Resources/PrivacyInfo.xcprivacy diff --git a/apps/swift/Resources/Info-iOS.plist b/apps/swift/Resources/Info-iOS.plist index 254785a61f..275aa2454d 100644 --- a/apps/swift/Resources/Info-iOS.plist +++ b/apps/swift/Resources/Info-iOS.plist @@ -60,8 +60,12 @@ NSAllowsLocalNetworking + NSCameraUsageDescription + This app requires access to your camera to let you take photos or scan items. NSLocationWhenInUseUsageDescription This app needs access to your location while you are using it. + NSPhotoLibraryUsageDescription + This app needs access to your photo library to let you upload or choose photos. PACKRAT_ENV $(PACKRAT_ENV) SENTRY_DSN diff --git a/apps/swift/Resources/PackRat-iOS.entitlements b/apps/swift/Resources/PackRat-iOS.entitlements index 0c67376eba..a812db506f 100644 --- a/apps/swift/Resources/PackRat-iOS.entitlements +++ b/apps/swift/Resources/PackRat-iOS.entitlements @@ -1,5 +1,10 @@ - + + com.apple.developer.applesignin + + Default + + diff --git a/apps/swift/Resources/PrivacyInfo.xcprivacy b/apps/swift/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..70f21ef389 --- /dev/null +++ b/apps/swift/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,76 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + NSPrivacyCollectedDataTypeTracking + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePerformanceData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + NSPrivacyCollectedDataTypeTracking + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + NSPrivacyCollectedDataTypeTracking + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + diff --git a/apps/swift/project.yml b/apps/swift/project.yml index 831e3d6773..ba6d295b51 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -63,9 +63,13 @@ targets: - Sources/PackRatShared - path: Resources/Assets.xcassets buildPhase: resources + - path: Resources/PrivacyInfo.xcprivacy + buildPhase: resources entitlements: path: Resources/PackRat-iOS.entitlements - properties: {} + properties: + com.apple.developer.applesignin: + - Default info: path: Resources/Info-iOS.plist properties: @@ -90,6 +94,8 @@ targets: UIApplicationSceneManifest: UIApplicationSupportsMultipleScenes: true NSLocationWhenInUseUsageDescription: "This app needs access to your location while you are using it." + NSCameraUsageDescription: "This app requires access to your camera to let you take photos or scan items." + NSPhotoLibraryUsageDescription: "This app needs access to your photo library to let you upload or choose photos." ITSAppUsesNonExemptEncryption: false PACKRAT_ENV: $(PACKRAT_ENV) SENTRY_DSN: $(SENTRY_DSN) @@ -158,6 +164,8 @@ targets: buildPhase: resources - path: Resources/WatchAssets.xcassets buildPhase: resources + - path: Resources/PrivacyInfo.xcprivacy + buildPhase: resources info: path: Resources/Info-watchOS.plist properties: @@ -189,6 +197,8 @@ targets: - Sources/PackRatShared - path: Resources/Assets.xcassets buildPhase: resources + - path: Resources/PrivacyInfo.xcprivacy + buildPhase: resources entitlements: path: Resources/PackRat-macOS.entitlements properties: From 69545fc25005377ff3239773e8a1ce063db8f681 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 04:33:28 -0600 Subject: [PATCH 30/81] =?UTF-8?q?=E2=8C=9A=20fix:=20embed=20Swift=20watch?= =?UTF-8?q?=20companion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/README.md | 6 ++-- apps/swift/Resources/Info-watchOS.plist | 2 +- apps/swift/project.yml | 20 +++++++---- .../__tests__/testflight-config.test.ts | 10 +++++- .../scripts/capture-visual-screenshots.ts | 4 ++- apps/swift/scripts/lib/testflight-config.ts | 10 +++++- apps/swift/scripts/upload-testflight.ts | 2 ++ apps/swift/scripts/watch-sync-smoke.ts | 33 ++++++++++++++----- 8 files changed, 67 insertions(+), 20 deletions(-) diff --git a/apps/swift/README.md b/apps/swift/README.md index 45a9a36f62..992be513e8 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -77,10 +77,12 @@ The Swift iOS app has two TestFlight identities: - Replacement beta: `com.andrewbierman.packrat`, display name `PackRat`. This is the existing Expo/App Store listing and is the only lane that can validate a - seamless update for existing testers. + seamless update for existing testers. Its embedded watchOS companion uses + `com.andrewbierman.packrat.watchkitapp`. - Side-by-side beta: `com.andrewbierman.packrat.swift`, display name `PackRat Swift`. This is useful for parallel Swift QA, but iOS treats it as a - separate app with separate install, keychain, and app container state. + separate app with separate install, keychain, and app container state. Its + embedded watchOS companion uses `com.andrewbierman.packrat.swift.watchkitapp`. Upload commands require an explicit lane so we do not accidentally test the wrong App Store Connect record: diff --git a/apps/swift/Resources/Info-watchOS.plist b/apps/swift/Resources/Info-watchOS.plist index bd741ae16b..8330ec0942 100644 --- a/apps/swift/Resources/Info-watchOS.plist +++ b/apps/swift/Resources/Info-watchOS.plist @@ -27,6 +27,6 @@ WKApplication WKCompanionAppBundleIdentifier - com.andrewbierman.packrat.swift + $(PACKRAT_COMPANION_BUNDLE_IDENTIFIER) diff --git a/apps/swift/project.yml b/apps/swift/project.yml index ba6d295b51..ff1efee0d4 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -137,9 +137,12 @@ targets: product: Sentry - package: GoogleSignIn product: GoogleSignIn - # PackRat-Watch is intentionally not embedded in distribution builds yet. - # Keep the companion target buildable while Swift beta TestFlight ships as - # a side-by-side iOS app first. + - target: PackRat-Watch + embed: true + codeSign: true + copy: + destination: wrapper + subpath: Watch settings: base: SWIFT_VERSION: "5.9" @@ -147,7 +150,10 @@ targets: CURRENT_PROJECT_VERSION: "2026071601" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU - PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift + PACKRAT_IOS_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift + PRODUCT_BUNDLE_IDENTIFIER: $(PACKRAT_IOS_BUNDLE_IDENTIFIER) + PACKRAT_COMPANION_BUNDLE_IDENTIFIER: $(PACKRAT_IOS_BUNDLE_IDENTIFIER) + PACKRAT_WATCH_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift.watchkitapp PACKRAT_DISPLAY_NAME: PackRat Swift PRODUCT_MODULE_NAME: PackRat TARGETED_DEVICE_FAMILY: "1,2" @@ -174,7 +180,7 @@ targets: CFBundleShortVersionString: "2.0.29" CFBundleVersion: "2026071601" WKApplication: true - WKCompanionAppBundleIdentifier: com.andrewbierman.packrat.swift + WKCompanionAppBundleIdentifier: $(PACKRAT_COMPANION_BUNDLE_IDENTIFIER) ITSAppUsesNonExemptEncryption: false settings: base: @@ -183,7 +189,9 @@ targets: CURRENT_PROJECT_VERSION: "2026071601" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU - PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift.watchkitapp + PACKRAT_WATCH_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift.watchkitapp + PRODUCT_BUNDLE_IDENTIFIER: $(PACKRAT_WATCH_BUNDLE_IDENTIFIER) + PACKRAT_COMPANION_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift PACKRAT_DISPLAY_NAME: PackRat Swift PRODUCT_MODULE_NAME: PackRatWatch ASSETCATALOG_COMPILER_APPICON_NAME: WatchAppIcon diff --git a/apps/swift/scripts/__tests__/testflight-config.test.ts b/apps/swift/scripts/__tests__/testflight-config.test.ts index 17531a813e..3e19f99be6 100644 --- a/apps/swift/scripts/__tests__/testflight-config.test.ts +++ b/apps/swift/scripts/__tests__/testflight-config.test.ts @@ -34,6 +34,8 @@ describe('parseTestFlightUploadConfig', () => { scheme: 'PackRat-iOS', configuration: 'Release', bundleId: 'com.andrewbierman.packrat.swift', + watchBundleId: 'com.andrewbierman.packrat.swift.watchkitapp', + companionBundleId: 'com.andrewbierman.packrat.swift', displayName: 'PackRat Swift', buildNumber: '123', apiEnvironment: 'production', @@ -53,6 +55,8 @@ describe('parseTestFlightUploadConfig', () => { scheme: 'PackRat-iOS', configuration: 'Release', bundleId: 'com.andrewbierman.packrat', + watchBundleId: 'com.andrewbierman.packrat.watchkitapp', + companionBundleId: 'com.andrewbierman.packrat', displayName: 'PackRat', buildNumber: '456', apiEnvironment: 'production', @@ -60,7 +64,9 @@ describe('parseTestFlightUploadConfig', () => { expect(xcodeArchiveOverrides({ config, teamId: 'TEAM123' })).toEqual([ 'CURRENT_PROJECT_VERSION=456', 'DEVELOPMENT_TEAM=TEAM123', - 'PRODUCT_BUNDLE_IDENTIFIER=com.andrewbierman.packrat', + 'PACKRAT_IOS_BUNDLE_IDENTIFIER=com.andrewbierman.packrat', + 'PACKRAT_WATCH_BUNDLE_IDENTIFIER=com.andrewbierman.packrat.watchkitapp', + 'PACKRAT_COMPANION_BUNDLE_IDENTIFIER=com.andrewbierman.packrat', 'PACKRAT_DISPLAY_NAME=PackRat', ]); }); @@ -78,6 +84,8 @@ describe('parseTestFlightUploadConfig', () => { scheme: 'PackRat-iOS-Staging', configuration: 'Staging', bundleId: 'com.andrewbierman.packrat', + watchBundleId: 'com.andrewbierman.packrat.watchkitapp', + companionBundleId: 'com.andrewbierman.packrat', displayName: 'PackRat', apiEnvironment: 'dev', }); diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index 6207a33df2..5313db6436 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -81,6 +81,8 @@ const MACOS_SCHEME_PATH = resolve( SWIFT_DIR, 'PackRat.xcodeproj/xcshareddata/xcschemes/PackRat-macOS.xcscheme', ); +const WATCH_BUNDLE_ID = + nodeEnv.PACKRAT_WATCH_BUNDLE_ID ?? 'com.andrewbierman.packrat.swift.watchkitapp'; const EMAIL_RE = createRegExp( oneOrMore(charIn('A-Z0-9._%+-')), '@', @@ -1395,7 +1397,7 @@ async function launchWatchRouteWithRetry(options: { 'launch', '--terminate-running-process', deviceId, - 'com.andrewbierman.packrat.watchkitapp', + WATCH_BUNDLE_ID, ], { cwd: SWIFT_DIR, diff --git a/apps/swift/scripts/lib/testflight-config.ts b/apps/swift/scripts/lib/testflight-config.ts index 3cf23731b2..a05a711bcc 100644 --- a/apps/swift/scripts/lib/testflight-config.ts +++ b/apps/swift/scripts/lib/testflight-config.ts @@ -7,6 +7,8 @@ export type TestFlightUploadConfig = { scheme: string; configuration: string; bundleId: string; + watchBundleId: string; + companionBundleId: string; displayName: string; buildNumber: string; apiEnvironment: 'dev' | 'production'; @@ -21,6 +23,8 @@ export class TestFlightConfigError extends Error { const SIDE_BY_SIDE_BUNDLE_ID = 'com.andrewbierman.packrat.swift'; const REPLACEMENT_BUNDLE_ID = 'com.andrewbierman.packrat'; +const SIDE_BY_SIDE_WATCH_BUNDLE_ID = 'com.andrewbierman.packrat.swift.watchkitapp'; +const REPLACEMENT_WATCH_BUNDLE_ID = 'com.andrewbierman.packrat.watchkitapp'; export function parseTestFlightUploadConfig(input: { argv: readonly string[]; @@ -52,6 +56,8 @@ export function parseTestFlightUploadConfig(input: { scheme: staging ? 'PackRat-iOS-Staging' : 'PackRat-iOS', configuration: staging ? 'Staging' : 'Release', bundleId: replacement ? REPLACEMENT_BUNDLE_ID : SIDE_BY_SIDE_BUNDLE_ID, + watchBundleId: replacement ? REPLACEMENT_WATCH_BUNDLE_ID : SIDE_BY_SIDE_WATCH_BUNDLE_ID, + companionBundleId: replacement ? REPLACEMENT_BUNDLE_ID : SIDE_BY_SIDE_BUNDLE_ID, displayName: replacement ? 'PackRat' : 'PackRat Swift', buildNumber, apiEnvironment: staging ? 'dev' : 'production', @@ -66,7 +72,9 @@ export function xcodeArchiveOverrides(input: { return [ `CURRENT_PROJECT_VERSION=${config.buildNumber}`, `DEVELOPMENT_TEAM=${teamId}`, - `PRODUCT_BUNDLE_IDENTIFIER=${config.bundleId}`, + `PACKRAT_IOS_BUNDLE_IDENTIFIER=${config.bundleId}`, + `PACKRAT_WATCH_BUNDLE_IDENTIFIER=${config.watchBundleId}`, + `PACKRAT_COMPANION_BUNDLE_IDENTIFIER=${config.companionBundleId}`, `PACKRAT_DISPLAY_NAME=${config.displayName}`, ]; } diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index 3aa4102bb9..c1bd17b97b 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -89,6 +89,8 @@ function printPreflight(input: { config: TestFlightUploadConfig; teamId?: string safeJsonStringify({ lane: config.lane, bundleId: config.bundleId, + watchBundleId: config.watchBundleId, + companionBundleId: config.companionBundleId, displayName: config.displayName, scheme: config.scheme, configuration: config.configuration, diff --git a/apps/swift/scripts/watch-sync-smoke.ts b/apps/swift/scripts/watch-sync-smoke.ts index ee93080e9c..04087a2905 100644 --- a/apps/swift/scripts/watch-sync-smoke.ts +++ b/apps/swift/scripts/watch-sync-smoke.ts @@ -20,8 +20,14 @@ type SimulatorPair = { const REPO_ROOT = resolve(import.meta.dir, '../../..'); const SWIFT_DIR = resolve(REPO_ROOT, 'apps/swift'); const ARTIFACT_DIR = resolve(REPO_ROOT, 'artifacts/screenshots-latest'); -const IOS_BUNDLE_ID = 'com.andrewbierman.packrat'; -const WATCH_BUNDLE_ID = 'com.andrewbierman.packrat.watchkitapp'; +const IOS_BUNDLE_ID = nodeEnv.PACKRAT_IOS_BUNDLE_ID ?? 'com.andrewbierman.packrat.swift'; +const WATCH_BUNDLE_ID = + nodeEnv.PACKRAT_WATCH_BUNDLE_ID ?? 'com.andrewbierman.packrat.swift.watchkitapp'; +const KNOWN_IOS_BUNDLE_IDS = ['com.andrewbierman.packrat', 'com.andrewbierman.packrat.swift']; +const KNOWN_WATCH_BUNDLE_IDS = [ + 'com.andrewbierman.packrat.watchkitapp', + 'com.andrewbierman.packrat.swift.watchkitapp', +]; const WAIT_MS = Number(nodeEnv.PACKRAT_WATCH_SYNC_WAIT_MS ?? 45_000); // biome-ignore lint/complexity/useMaxParams: command wrappers read like shell invocations. @@ -202,7 +208,7 @@ async function waitForWatchSnapshot(watchId: string, timeoutMs: number): Promise if (container) { const preferences = resolve( container, - 'Library/Preferences/com.andrewbierman.packrat.watchkitapp.plist', + `Library/Preferences/${WATCH_BUNDLE_ID}.plist`, ); const payload = outputOrNull('/usr/libexec/PlistBuddy', [ '-c', @@ -256,16 +262,27 @@ async function main() { const iosAppPath = appPath('PackRat-iOS', phoneDestination); const standaloneWatchAppPath = appPath('PackRat-Watch', watchDestination); - const embeddedWatchAppPath = resolve(iosAppPath, 'Watch/PackRat-Watch.app'); + const embeddedWatchCandidates = [ + resolve(iosAppPath, 'PlugIns/PackRat-Watch.app'), + resolve(iosAppPath, 'Watch/PackRat-Watch.app'), + ]; + const embeddedWatchAppPath = embeddedWatchCandidates.find((candidate) => existsSync(candidate)); if (!existsSync(iosAppPath)) throw new Error(`Missing iOS app at ${iosAppPath}`); - if (!existsSync(embeddedWatchAppPath)) - throw new Error(`Missing embedded Watch app at ${embeddedWatchAppPath}`); + if (!embeddedWatchAppPath) { + throw new Error(`Missing embedded Watch app at ${embeddedWatchCandidates.join(' or ')}`); + } if (!existsSync(standaloneWatchAppPath)) throw new Error(`Missing Watch app at ${standaloneWatchAppPath}`); console.log('-> Installing apps'); - run('xcrun', ['simctl', 'uninstall', phoneId, IOS_BUNDLE_ID], { allowFailure: true }); - run('xcrun', ['simctl', 'uninstall', watchId, WATCH_BUNDLE_ID], { allowFailure: true }); + for (const bundleId of KNOWN_IOS_BUNDLE_IDS) { + run('xcrun', ['simctl', 'terminate', phoneId, bundleId], { allowFailure: true }); + run('xcrun', ['simctl', 'uninstall', phoneId, bundleId], { allowFailure: true }); + } + for (const bundleId of KNOWN_WATCH_BUNDLE_IDS) { + run('xcrun', ['simctl', 'terminate', watchId, bundleId], { allowFailure: true }); + run('xcrun', ['simctl', 'uninstall', watchId, bundleId], { allowFailure: true }); + } await installAppWithRetry(phoneId, IOS_BUNDLE_ID, iosAppPath); await installAppWithRetry(watchId, WATCH_BUNDLE_ID, standaloneWatchAppPath); await waitForInstalledApp(phoneId, IOS_BUNDLE_ID); From aaebe117e1f9b5c9b09fd3b75e5ff004593b0bba Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 04:37:09 -0600 Subject: [PATCH 31/81] =?UTF-8?q?=F0=9F=8E=A8=20format=20Swift=20watch=20s?= =?UTF-8?q?cripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/scripts/capture-visual-screenshots.ts | 8 +------- apps/swift/scripts/watch-sync-smoke.ts | 5 +---- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index 5313db6436..133b443fdd 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -1392,13 +1392,7 @@ async function launchWatchRouteWithRetry(options: { } const result = spawnSync( 'xcrun', - [ - 'simctl', - 'launch', - '--terminate-running-process', - deviceId, - WATCH_BUNDLE_ID, - ], + ['simctl', 'launch', '--terminate-running-process', deviceId, WATCH_BUNDLE_ID], { cwd: SWIFT_DIR, env: { ...Bun.env, ...env }, diff --git a/apps/swift/scripts/watch-sync-smoke.ts b/apps/swift/scripts/watch-sync-smoke.ts index 04087a2905..510f66ab19 100644 --- a/apps/swift/scripts/watch-sync-smoke.ts +++ b/apps/swift/scripts/watch-sync-smoke.ts @@ -206,10 +206,7 @@ async function waitForWatchSnapshot(watchId: string, timeoutMs: number): Promise 'data', ]); if (container) { - const preferences = resolve( - container, - `Library/Preferences/${WATCH_BUNDLE_ID}.plist`, - ); + const preferences = resolve(container, `Library/Preferences/${WATCH_BUNDLE_ID}.plist`); const payload = outputOrNull('/usr/libexec/PlistBuddy', [ '-c', 'Print :watch.snapshot', From dd0e351f03551e24efc856a63dea394c257b53e7 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 05:04:27 -0600 Subject: [PATCH 32/81] =?UTF-8?q?=E2=9C=85=20fail=20Swift=20smoke=20on=20t?= =?UTF-8?q?est=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index f2d50fe4d3..c2412d580d 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -79,6 +79,7 @@ jobs: E2E_EMAIL: ${{ secrets.E2E_EMAIL }} E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }} run: | + set -o pipefail cd apps/swift # Use signing-bypass on macOS so CI can build/test without a Mac Development cert # provisioned for the runner. iOS Simulator never needs signing, but @@ -117,7 +118,12 @@ jobs: if [ -d "/tmp/${{ matrix.scheme }}-smoke.xcresult" ]; then xcrun xcresulttool get test-results summary \ --path "/tmp/${{ matrix.scheme }}-smoke.xcresult" --compact \ + | tee "/tmp/${{ matrix.scheme }}-smoke-summary.json" \ | jq '{ result, totalTestCount, passedTests, failedTests, skippedTests, expectedFailures }' || true + if [ -f "/tmp/${{ matrix.scheme }}-smoke-summary.json" ]; then + jq -e '.result == "Passed" and (.failedTests // 0) == 0' \ + "/tmp/${{ matrix.scheme }}-smoke-summary.json" >/dev/null + fi fi - name: Upload xcresult on failure From 50ace55365d762bc014ecbd25c4d47d46c4b6cf5 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:08:42 -0600 Subject: [PATCH 33/81] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20bump=20Nuke=20for=20?= =?UTF-8?q?Swift=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/project.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/swift/project.yml b/apps/swift/project.yml index ff1efee0d4..9730f3ccf4 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -24,7 +24,7 @@ configFiles: packages: Nuke: url: https://github.com/kean/Nuke - from: "12.0.0" + from: "13.0.0" MarkdownUI: url: https://github.com/gonzalezreal/swift-markdown-ui from: "2.4.0" From 29bc5c60ed68e31375ee80771f79a164014cbf04 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:08:42 -0600 Subject: [PATCH 34/81] =?UTF-8?q?=E2=9C=85=20stabilize=20seeded=20Swift=20?= =?UTF-8?q?login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Sources/PackRat/Network/AuthManager.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/swift/Sources/PackRat/Network/AuthManager.swift b/apps/swift/Sources/PackRat/Network/AuthManager.swift index 60aeb1456c..6f82dedc6a 100644 --- a/apps/swift/Sources/PackRat/Network/AuthManager.swift +++ b/apps/swift/Sources/PackRat/Network/AuthManager.swift @@ -43,6 +43,9 @@ final class AuthManager { /// braces guarantee for tests / mock transports. func login(email: String, password: String) async throws { if seedE2ELoginIfAllowed(email: email, password: password) { + await MainActor.run { + finishSeededE2ELogin() + } return } @@ -382,10 +385,13 @@ final class AuthManager { return false } - seedE2EAuthenticatedUser() return true } + private func finishSeededE2ELogin() { + seedE2EAuthenticatedUser() + } + private static var e2eLoginSeedAllowed: Bool { let value = ProcessInfo.processInfo.environment["PACKRAT_E2E_ALLOW_LOGIN_SEED"] ?? "" return value == "1" || value.caseInsensitiveCompare("true") == .orderedSame From fac932f33dbed491ad2e90b8e5d84ba2c0ded10c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:08:42 -0600 Subject: [PATCH 35/81] =?UTF-8?q?=F0=9F=91=B7=20align=20macOS=20smoke=20si?= =?UTF-8?q?gning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index c2412d580d..57d475ce1a 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -95,7 +95,13 @@ jobs: "PACKRAT_ENV=local" ) else - CODE_SIGN_ARGS=(CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO) + CODE_SIGN_ARGS=( + CODE_SIGN_STYLE=Manual + DEVELOPMENT_TEAM= + CODE_SIGN_IDENTITY=- + CODE_SIGNING_ALLOWED=YES + CODE_SIGNING_REQUIRED=NO + ) fi DESTINATION="${{ matrix.destination }}" if [ "${{ matrix.scheme }}" = "PackRat-iOS" ]; then From 2172fbaef0fc1266e2449b5d59d534cfa221ea82 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:19:21 -0600 Subject: [PATCH 36/81] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20pin=20Nuke=20for=20X?= =?UTF-8?q?code=2016=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/project.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/swift/project.yml b/apps/swift/project.yml index 9730f3ccf4..25bd01f6b8 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -24,7 +24,7 @@ configFiles: packages: Nuke: url: https://github.com/kean/Nuke - from: "13.0.0" + exactVersion: "12.8.0" MarkdownUI: url: https://github.com/gonzalezreal/swift-markdown-ui from: "2.4.0" From 92410e5912a634d452a493d1624b3715535ea207 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:52:04 -0600 Subject: [PATCH 37/81] =?UTF-8?q?=F0=9F=9A=80=20harden=20TestFlight=20uplo?= =?UTF-8?q?ad=20preflight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/testflight-upload-cli.test.ts | 32 +++++++++++++++++++ apps/swift/scripts/upload-testflight.ts | 28 +++++++++++----- packages/env/src/node.ts | 2 ++ 3 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 apps/swift/scripts/__tests__/testflight-upload-cli.test.ts diff --git a/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts new file mode 100644 index 0000000000..ade26cad31 --- /dev/null +++ b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts @@ -0,0 +1,32 @@ +import { execFileSync } from 'node:child_process'; +import { resolve } from 'node:path'; +import { env as currentEnv } from 'node:process'; +import { describe, expect, it } from 'vitest'; + +const repoRoot = resolve(__dirname, '../../../..'); +const script = resolve(repoRoot, 'apps/swift/scripts/upload-testflight.ts'); + +describe('upload-testflight CLI', () => { + it('uses BUILD_NUMBER in dry-run preflight output', () => { + const output = execFileSync( + 'bun', + [script, '--replacement', '--production', '--dry-run'], + { + cwd: repoRoot, + encoding: 'utf8', + env: { ...currentEnv, APPLE_ASC_PROVIDER: 'PackRatProvider', BUILD_NUMBER: '2026071801' }, + }, + ); + + const preflight = JSON.parse(output); + expect(preflight).toMatchObject({ + lane: 'replacement', + bundleId: 'com.andrewbierman.packrat', + displayName: 'PackRat', + buildNumber: '2026071801', + apiEnvironment: 'production', + ascProvider: 'PackRatProvider', + }); + expect(preflight.archiveOverrides).toContain('CURRENT_PROJECT_VERSION=2026071801'); + }); +}); diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index c1bd17b97b..4632138b3f 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -15,9 +15,11 @@ * Required env (put in apps/swift/.env.local, gitignored): * APPLE_ID your Apple ID email * APPLE_APP_PASSWORD app-specific password (xxxx-xxxx-xxxx-xxxx) - * APPLE_TEAM_ID the team that owns the record (e.g. 7WV9JYCW55) + * APPLE_TEAM_ID Apple Developer Team ID used for signing * * Optional env: + * APPLE_ASC_PROVIDER App Store Connect provider short name for altool; + * defaults to APPLE_TEAM_ID when omitted * BUILD_NUMBER CFBundleVersion for this upload (default: timestamp) * * Flags: @@ -39,6 +41,7 @@ import { execFileSync } from 'node:child_process'; import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { nodeEnv } from '@packrat/env/node'; import { safeJsonStringify } from '@packrat/utils'; import { parseTestFlightUploadConfig, @@ -73,6 +76,7 @@ let uploadConfig: TestFlightUploadConfig; try { uploadConfig = parseTestFlightUploadConfig({ argv: process.argv.slice(2), + env: { BUILD_NUMBER: nodeEnv.BUILD_NUMBER }, }); } catch (error) { if (error instanceof TestFlightConfigError) { @@ -82,8 +86,16 @@ try { throw error; } -function printPreflight(input: { config: TestFlightUploadConfig; teamId?: string }) { - const { config, teamId = '' } = input; +function printPreflight(input: { + config: TestFlightUploadConfig; + teamId?: string; + ascProvider?: string; +}) { + const { + config, + teamId = '', + ascProvider = '', + } = input; const archiveOverrides = xcodeArchiveOverrides({ config, teamId }); console.log( safeJsonStringify({ @@ -96,18 +108,17 @@ function printPreflight(input: { config: TestFlightUploadConfig; teamId?: string configuration: config.configuration, apiEnvironment: config.apiEnvironment, buildNumber: config.buildNumber, + ascProvider, archiveOverrides, }), ); } if (uploadConfig.dryRun) { - printPreflight({ config: uploadConfig }); + printPreflight({ config: uploadConfig, ascProvider: nodeEnv.APPLE_ASC_PROVIDER }); process.exit(0); } -const { nodeEnv } = await import('@packrat/env/node'); - function req(input: { name: 'APPLE_ID' | 'APPLE_APP_PASSWORD' | 'APPLE_TEAM_ID' }): string { const v = nodeEnv[input.name]; if (!v) { @@ -124,7 +135,8 @@ if (nodeEnv.BUILD_NUMBER) { const appleId = req({ name: 'APPLE_ID' }); const appPassword = req({ name: 'APPLE_APP_PASSWORD' }); const teamId = req({ name: 'APPLE_TEAM_ID' }); -printPreflight({ config: uploadConfig, teamId }); +const ascProvider = nodeEnv.APPLE_ASC_PROVIDER ?? teamId; +printPreflight({ config: uploadConfig, teamId, ascProvider }); const work = mkdtempSync(join(tmpdir(), 'packrat-tf-')); const archivePath = join(work, 'PackRat.xcarchive'); @@ -210,7 +222,7 @@ run({ '--password', appPassword, '--asc-provider', - teamId, + ascProvider, ], }); diff --git a/packages/env/src/node.ts b/packages/env/src/node.ts index 4055fa820d..9ddb9007b7 100644 --- a/packages/env/src/node.ts +++ b/packages/env/src/node.ts @@ -120,6 +120,7 @@ export const nodeEnvSchema = z.object({ APPLE_ID: z.string().email().optional(), APPLE_APP_PASSWORD: z.string().min(1).optional(), APPLE_TEAM_ID: z.string().min(1).optional(), + APPLE_ASC_PROVIDER: z.string().min(1).optional(), BUILD_NUMBER: z.string().regex(/^\d+$/).optional(), }); @@ -198,5 +199,6 @@ export const nodeEnv = nodeEnvSchema.parse({ APPLE_ID: process.env.APPLE_ID, APPLE_APP_PASSWORD: process.env.APPLE_APP_PASSWORD, APPLE_TEAM_ID: process.env.APPLE_TEAM_ID, + APPLE_ASC_PROVIDER: process.env.APPLE_ASC_PROVIDER, BUILD_NUMBER: process.env.BUILD_NUMBER, }); From 2d66d219d11c23819808856b1c0fc9150bafd5e4 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:52:04 -0600 Subject: [PATCH 38/81] =?UTF-8?q?=F0=9F=93=A6=20honor=20Swift=20archive=20?= =?UTF-8?q?build=20numbers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Resources/Info-iOS.plist | 4 ++-- apps/swift/Resources/Info-macOS.plist | 4 ++-- apps/swift/Resources/Info-watchOS.plist | 4 ++-- apps/swift/project.yml | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/swift/Resources/Info-iOS.plist b/apps/swift/Resources/Info-iOS.plist index 275aa2454d..3d506f6bcb 100644 --- a/apps/swift/Resources/Info-iOS.plist +++ b/apps/swift/Resources/Info-iOS.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.29 + $(MARKETING_VERSION) CFBundleURLTypes @@ -48,7 +48,7 @@ CFBundleVersion - 2026071601 + $(CURRENT_PROJECT_VERSION) GOOGLE_IOS_CLIENT_ID 993694750638-97t0vhfml04u2avrlbve22jbs9qcinbc.apps.googleusercontent.com ITSAppUsesNonExemptEncryption diff --git a/apps/swift/Resources/Info-macOS.plist b/apps/swift/Resources/Info-macOS.plist index 09dbc4e89f..8ba5115ee1 100644 --- a/apps/swift/Resources/Info-macOS.plist +++ b/apps/swift/Resources/Info-macOS.plist @@ -19,9 +19,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.29 + $(MARKETING_VERSION) CFBundleVersion - 2026071601 + $(CURRENT_PROJECT_VERSION) NSAppTransportSecurity NSAllowsLocalNetworking diff --git a/apps/swift/Resources/Info-watchOS.plist b/apps/swift/Resources/Info-watchOS.plist index 8330ec0942..6bc14b5237 100644 --- a/apps/swift/Resources/Info-watchOS.plist +++ b/apps/swift/Resources/Info-watchOS.plist @@ -19,9 +19,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.29 + $(MARKETING_VERSION) CFBundleVersion - 2026071601 + $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption WKApplication diff --git a/apps/swift/project.yml b/apps/swift/project.yml index 25bd01f6b8..0ca1537233 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -75,8 +75,8 @@ targets: properties: CFBundleDisplayName: $(PACKRAT_DISPLAY_NAME) CFBundleIconName: AppIcon - CFBundleShortVersionString: "2.0.29" - CFBundleVersion: "2026071601" + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS: true # All four orientations are required for iPad (device family "1,2"); # App Store validation (error 90474) rejects the bundle otherwise. @@ -177,8 +177,8 @@ targets: properties: CFBundleDisplayName: $(PACKRAT_DISPLAY_NAME) CFBundleIconName: WatchAppIcon - CFBundleShortVersionString: "2.0.29" - CFBundleVersion: "2026071601" + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) WKApplication: true WKCompanionAppBundleIdentifier: $(PACKRAT_COMPANION_BUNDLE_IDENTIFIER) ITSAppUsesNonExemptEncryption: false @@ -217,8 +217,8 @@ targets: properties: CFBundleDisplayName: $(PACKRAT_DISPLAY_NAME) CFBundleIconName: AppIcon - CFBundleShortVersionString: "2.0.29" - CFBundleVersion: "2026071601" + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) NSPrincipalClass: NSApplication NSHighResolutionCapable: true PACKRAT_ENV: $(PACKRAT_ENV) From 730ab56284101a6c5991bce88b2e72c1d72b6913 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:58:46 -0600 Subject: [PATCH 39/81] =?UTF-8?q?=F0=9F=8E=A8=20format=20TestFlight=20uplo?= =?UTF-8?q?ad=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/testflight-upload-cli.test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts index ade26cad31..2aa4eb60f6 100644 --- a/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts +++ b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts @@ -8,15 +8,11 @@ const script = resolve(repoRoot, 'apps/swift/scripts/upload-testflight.ts'); describe('upload-testflight CLI', () => { it('uses BUILD_NUMBER in dry-run preflight output', () => { - const output = execFileSync( - 'bun', - [script, '--replacement', '--production', '--dry-run'], - { - cwd: repoRoot, - encoding: 'utf8', - env: { ...currentEnv, APPLE_ASC_PROVIDER: 'PackRatProvider', BUILD_NUMBER: '2026071801' }, - }, - ); + const output = execFileSync('bun', [script, '--replacement', '--production', '--dry-run'], { + cwd: repoRoot, + encoding: 'utf8', + env: { ...currentEnv, APPLE_ASC_PROVIDER: 'PackRatProvider', BUILD_NUMBER: '2026071801' }, + }); const preflight = JSON.parse(output); expect(preflight).toMatchObject({ From 02c6c86e6437ae41483d6c7bd0eb4b3d23460e1e Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:01:00 -0600 Subject: [PATCH 40/81] =?UTF-8?q?=F0=9F=91=B7=20install=20xcodegen=20for?= =?UTF-8?q?=20Swift=20E2E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index 191c98ea89..feb8272938 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -93,11 +93,13 @@ jobs: uses: oven-sh/setup-bun@v2 with: bun-version: latest - cache: true - name: Install dependencies run: bun install --frozen-lockfile + - name: Install xcodegen + run: brew install xcodegen + - name: Resolve Swift E2E target run: | target="${{ inputs.api_environment || 'configured-secret' }}" @@ -223,11 +225,13 @@ jobs: uses: oven-sh/setup-bun@v2 with: bun-version: latest - cache: true - name: Install dependencies run: bun install --frozen-lockfile + - name: Install xcodegen + run: brew install xcodegen + - name: Resolve Swift E2E target run: | target="${{ inputs.api_environment || 'configured-secret' }}" From 19ff5978371a095e7709c963523e25e9cfe84edc Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:22:31 -0600 Subject: [PATCH 41/81] =?UTF-8?q?=F0=9F=91=B7=20boot=20simulator=20for=20S?= =?UTF-8?q?wift=20E2E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index feb8272938..f37009291e 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -286,6 +286,14 @@ jobs: E2E_TEST_EMAIL: ${{ env.E2E_EMAIL }} E2E_TEST_PASSWORD: ${{ env.E2E_PASSWORD }} + - name: Boot iOS simulator + run: | + DEVICE_ID=$(xcrun simctl create Swift-E2E "iPhone 17" \ + "$(xcrun simctl list runtimes -j | jq -r '.runtimes[] | select(.identifier | contains("iOS-")) | .identifier' | sort -V | tail -1)") + xcrun simctl boot "$DEVICE_ID" + xcrun simctl bootstatus "$DEVICE_ID" + echo "IOS_SIMULATOR_ID=$DEVICE_ID" >> "$GITHUB_ENV" + - name: Run iOS Swift UI E2E run: bun run e2e:swift:ios From 5d6f49196d5abb95cb982fbd54666141c452727c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 15:06:55 -0600 Subject: [PATCH 42/81] =?UTF-8?q?=F0=9F=91=B7=20split=20Swift=20E2E=20test?= =?UTF-8?q?=20depth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index f37009291e..985f223caf 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -35,6 +35,14 @@ on: required: false type: boolean default: false + ios_test_plan: + description: "iOS test depth for manual runs" + required: false + type: choice + default: smoke + options: + - smoke + - full api_environment: description: "API target for manual runs" required: false @@ -295,7 +303,13 @@ jobs: echo "IOS_SIMULATOR_ID=$DEVICE_ID" >> "$GITHUB_ENV" - name: Run iOS Swift UI E2E - run: bun run e2e:swift:ios + run: | + plan="${{ inputs.ios_test_plan || 'full' }}" + if [ "$plan" = "full" ]; then + bun run e2e:swift:ios + else + bun run e2e:swift:ios-smoke + fi - name: Summarize iOS xcresult if: always() From 86d6cd64fa4120ce0795d111b75d84dd66a9c023 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 15:25:58 -0600 Subject: [PATCH 43/81] =?UTF-8?q?=F0=9F=91=B7=20add=20Swift=20TestFlight?= =?UTF-8?q?=20sanity=20plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 7 +++-- apps/swift/README.md | 8 ++++- apps/swift/TestPlans/iOS-Sanity.xctestplan | 36 ++++++++++++++++++++++ apps/swift/project.yml | 1 + apps/swift/scripts/__tests__/args.test.ts | 9 +++++- apps/swift/scripts/lib/args.ts | 10 ++++-- package.json | 1 + 7 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 apps/swift/TestPlans/iOS-Sanity.xctestplan diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index 985f223caf..cbcb1a61ca 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -39,8 +39,9 @@ on: description: "iOS test depth for manual runs" required: false type: choice - default: smoke + default: sanity options: + - sanity - smoke - full api_environment: @@ -307,8 +308,10 @@ jobs: plan="${{ inputs.ios_test_plan || 'full' }}" if [ "$plan" = "full" ]; then bun run e2e:swift:ios - else + elif [ "$plan" = "smoke" ]; then bun run e2e:swift:ios-smoke + else + bun run e2e:swift:ios-sanity fi - name: Summarize iOS xcresult diff --git a/apps/swift/README.md b/apps/swift/README.md index 992be513e8..3e3123e3d8 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -19,6 +19,7 @@ mkdir -p /Volumes/CrucialX10/tmp/andrewbierman ```sh bun run test:swift:runner bun run test:swift:unit +bun run e2e:swift:ios-sanity bun run e2e:swift:ios-smoke bun run e2e:swift:ios bun run e2e:swift:mac @@ -29,10 +30,15 @@ bun run e2e:swift:mac-ui `e2e:swift` defaults to iOS UI tests for compatibility with the original runner. All Xcode result bundles are written under `apps/swift/TestResults/`. +Sanity mode is the fastest TestFlight-style loading check: + +- `e2e:swift:ios-sanity`: iOS launch, guest entry, real login, and primary tabs. + Smoke modes are intentionally small PR gates: - `e2e:swift:mac-smoke`: macOS login, sidebar navigation, and pack create/add-item. -- `e2e:swift:ios-smoke`: iOS login, tab navigation, and pack create. +- `e2e:swift:ios-smoke`: iOS auth, guest persistence, feature gating, navigation, + search/filter/explore entry points, and weather entry. Full modes are the platform confidence gates: diff --git a/apps/swift/TestPlans/iOS-Sanity.xctestplan b/apps/swift/TestPlans/iOS-Sanity.xctestplan new file mode 100644 index 0000000000..497c665349 --- /dev/null +++ b/apps/swift/TestPlans/iOS-Sanity.xctestplan @@ -0,0 +1,36 @@ +{ + "configurations" : [ + { + "id" : "5E1A1F00-0003-4001-A000-000000000001", + "name" : "Default", + "options" : { + + } + } + ], + "defaultOptions" : { + "areLocalizationScreenshotsEnabled" : false, + "codeCoverage" : false, + "diagnosticCollectionPolicy" : "Never", + "language" : "en", + "region" : "US", + "testTimeoutsEnabled" : true, + "maximumTestExecutionTimeAllowance" : 60 + }, + "testTargets" : [ + { + "selectedTests" : [ + "AuthTests/testAuthWelcomeScreenAppears()", + "AuthTests/testContinueWithoutLoginOpensAppShell()", + "AuthTests/testSuccessfulLogin()", + "NavigationTests/testAllPrimaryTabsReachable()" + ], + "target" : { + "containerPath" : "container:PackRat.xcodeproj", + "identifier" : "PackRatUITests", + "name" : "PackRatUITests" + } + } + ], + "version" : 1 +} diff --git a/apps/swift/project.yml b/apps/swift/project.yml index 0ca1537233..ead6409ee9 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -381,6 +381,7 @@ schemes: - path: TestPlans/iOS-Full.xctestplan defaultPlan: true - path: TestPlans/iOS-Smoke.xctestplan + - path: TestPlans/iOS-Sanity.xctestplan targets: - PackRatTests - PackRatUITests diff --git a/apps/swift/scripts/__tests__/args.test.ts b/apps/swift/scripts/__tests__/args.test.ts index 1263fc4a73..1d2d93faca 100644 --- a/apps/swift/scripts/__tests__/args.test.ts +++ b/apps/swift/scripts/__tests__/args.test.ts @@ -10,11 +10,16 @@ describe('parseArgs', () => { expect(parseArgs(['--plan', 'smoke'])).toEqual({ plan: 'iOS-Smoke', passthrough: [] }); }); + it('resolves --plan sanity to iOS-Sanity', () => { + expect(parseArgs(['--plan', 'sanity'])).toEqual({ plan: 'iOS-Sanity', passthrough: [] }); + }); + it('resolves --plan full to iOS-Full', () => { expect(parseArgs(['--plan', 'full'])).toEqual({ plan: 'iOS-Full', passthrough: [] }); }); - it('accepts the canonical iOS-Smoke and iOS-Full names', () => { + it('accepts the canonical iOS-Sanity, iOS-Smoke, and iOS-Full names', () => { + expect(parseArgs(['--plan', 'iOS-Sanity']).plan).toBe('iOS-Sanity'); expect(parseArgs(['--plan', 'iOS-Smoke']).plan).toBe('iOS-Smoke'); expect(parseArgs(['--plan', 'iOS-Full']).plan).toBe('iOS-Full'); }); @@ -26,6 +31,7 @@ describe('parseArgs', () => { it('accepts legacy positional iOS mode aliases used by package scripts', () => { expect(parseArgs(['ios-ui'])).toEqual({ plan: 'iOS-Full', passthrough: [] }); expect(parseArgs(['ios-smoke'])).toEqual({ plan: 'iOS-Smoke', passthrough: [] }); + expect(parseArgs(['ios-sanity'])).toEqual({ plan: 'iOS-Sanity', passthrough: [] }); }); it('maps unit mode to the iOS unit test target instead of an xcodebuild action', () => { @@ -52,6 +58,7 @@ describe('parseArgs', () => { }); it('case-insensitive alias matching', () => { + expect(parseArgs(['--plan', 'SANITY']).plan).toBe('iOS-Sanity'); expect(parseArgs(['--plan', 'SMOKE']).plan).toBe('iOS-Smoke'); expect(parseArgs(['--plan', 'FULL']).plan).toBe('iOS-Full'); }); diff --git a/apps/swift/scripts/lib/args.ts b/apps/swift/scripts/lib/args.ts index 8b68f65e15..4b6e226ad1 100644 --- a/apps/swift/scripts/lib/args.ts +++ b/apps/swift/scripts/lib/args.ts @@ -1,18 +1,20 @@ -export type TestPlanName = 'iOS-Full' | 'iOS-Smoke'; +export type TestPlanName = 'iOS-Full' | 'iOS-Smoke' | 'iOS-Sanity'; export type ParsedArgs = { plan?: TestPlanName; passthrough: string[]; }; -const KNOWN_PLANS: TestPlanName[] = ['iOS-Full', 'iOS-Smoke']; +const KNOWN_PLANS: TestPlanName[] = ['iOS-Full', 'iOS-Smoke', 'iOS-Sanity']; const ALIASES: Record = { full: 'iOS-Full', smoke: 'iOS-Smoke', + sanity: 'iOS-Sanity', 'ios-ui': 'iOS-Full', 'ios-full': 'iOS-Full', 'ios-smoke': 'iOS-Smoke', + 'ios-sanity': 'iOS-Sanity', }; const POSITIONAL_MODES: Record = { @@ -51,7 +53,9 @@ export function parseArgs(argv: readonly string[]): ParsedArgs { if (a === '--plan') { const next = argv[i + 1]; if (!next || next.startsWith('-')) { - throw new ArgsError('--plan requires a value (smoke | full | iOS-Smoke | iOS-Full)'); + throw new ArgsError( + '--plan requires a value (sanity | smoke | full | iOS-Sanity | iOS-Smoke | iOS-Full)', + ); } plan = resolvePlan(next); i++; diff --git a/package.json b/package.json index a6d76ab0a9..a2deb0be89 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "configure:deps": "bun run .github/scripts/configure-deps.ts", "e2e:swift": "bun run apps/swift/scripts/run-e2e.ts", "e2e:swift:ios": "bun run apps/swift/scripts/run-e2e.ts --plan full", + "e2e:swift:ios-sanity": "bun run apps/swift/scripts/run-e2e.ts --plan sanity", "e2e:swift:ios-smoke": "bun run apps/swift/scripts/run-e2e.ts --plan smoke", "e2e:swift:mac": "bun run apps/swift/scripts/run-e2e-macos.ts --plan smoke", "e2e:swift:mac-smoke": "bun run apps/swift/scripts/run-e2e-macos.ts --plan smoke", From 5562004c413022402b6bf8b72c231b124657b553 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 15:29:13 -0600 Subject: [PATCH 44/81] =?UTF-8?q?=F0=9F=A7=AA=20harden=20Swift=20login=20U?= =?UTF-8?q?I=20submit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Tests/PackRatUITests/AppUITestCase.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift index a0baf954e2..d294d98766 100644 --- a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift +++ b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift @@ -161,13 +161,20 @@ class AppUITestCase: XCTestCase { } func submitLoginForm() { + let submitButton = app.buttons["login_submit"] + XCTAssertTrue(submitButton.waitForExistence(timeout: 5), "Login submit button must be visible") + XCTAssertTrue(submitButton.isEnabled, "Login submit button must be enabled after credentials are filled") #if os(macOS) // macOS can show a password/autofill popover over the submit button // after typing into SecureField. Escape dismisses it before tapping. app.typeText("\u{1b}") - app.buttons["login_submit"].tap() + submitButton.tap() #else - app.buttons["login_submit"].tap() + if submitButton.isHittable { + submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + } else { + app.typeText("\n") + } #endif } From d2b3f61ca38e9ac3ac23af6899c9d9e15e77a03c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 15:42:27 -0600 Subject: [PATCH 45/81] =?UTF-8?q?=F0=9F=A7=AA=20stabilize=20iOS=20login=20?= =?UTF-8?q?submit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Sources/PackRat/Features/Auth/LoginView.swift | 3 +++ apps/swift/Tests/PackRatUITests/AppUITestCase.swift | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift b/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift index 2b84135364..94bdc107dc 100644 --- a/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift +++ b/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift @@ -35,6 +35,9 @@ struct LoginView: View { SecureField("Password", text: $password) .textContentType(.password) + #if os(iOS) + .submitLabel(.go) + #endif .onSubmit { submit() } .padding(.horizontal, 14) .padding(.vertical, 12) diff --git a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift index d294d98766..8e93c05590 100644 --- a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift +++ b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift @@ -172,6 +172,10 @@ class AppUITestCase: XCTestCase { #else if submitButton.isHittable { submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + Thread.sleep(forTimeInterval: 0.25) + if submitButton.exists, submitButton.isHittable, submitButton.isEnabled { + submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + } } else { app.typeText("\n") } From bcafd75318a48ee7f7cdb643d6e2f1b7064fc89b Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 15:53:52 -0600 Subject: [PATCH 46/81] =?UTF-8?q?=F0=9F=A7=AA=20submit=20Swift=20login=20v?= =?UTF-8?q?ia=20keyboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Tests/PackRatUITests/AppUITestCase.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift index 8e93c05590..1d99bd2905 100644 --- a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift +++ b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift @@ -170,6 +170,8 @@ class AppUITestCase: XCTestCase { app.typeText("\u{1b}") submitButton.tap() #else + app.typeText("\n") + Thread.sleep(forTimeInterval: 0.5) if submitButton.isHittable { submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() Thread.sleep(forTimeInterval: 0.25) From 663ba09ff0c6d69dd68ff8a31df7d78c686a6e10 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 16:14:07 -0600 Subject: [PATCH 47/81] =?UTF-8?q?=F0=9F=A7=AA=20surface=20Swift=20auth=20f?= =?UTF-8?q?ailures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PackRat/Features/Auth/LoginView.swift | 22 ++++++++++++++++++- .../Sources/PackRat/Network/APIClient.swift | 18 +++++++++++++-- .../Tests/PackRatUITests/AppUITestCase.swift | 19 ++++++++++++++-- .../Tests/PackRatUITests/AuthTests.swift | 6 ++++- 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift b/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift index 94bdc107dc..48fe2f0cfe 100644 --- a/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift +++ b/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift @@ -46,7 +46,7 @@ struct LoginView: View { .authGroupedSurface() if let error { - InlineErrorView(message: error) + LoginInlineErrorView(message: error) } VStack(spacing: 12) { @@ -179,6 +179,26 @@ struct LoginView: View { #endif } +private struct LoginInlineErrorView: View { + let message: String + + var body: some View { + HStack(spacing: 6) { + Image(systemName: "exclamationmark.circle.fill") + .foregroundStyle(.red) + Text(message) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(3) + } + .padding(.horizontal, 12) + .padding(.vertical, 8) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.red.opacity(0.08), in: RoundedRectangle(cornerRadius: 10, style: .continuous)) + .accessibilityIdentifier("login_error") + } +} + @ViewBuilder func authContainer(@ViewBuilder content: () -> Content) -> some View { #if os(macOS) diff --git a/apps/swift/Sources/PackRat/Network/APIClient.swift b/apps/swift/Sources/PackRat/Network/APIClient.swift index 2c16d1b51e..bfda6310b9 100644 --- a/apps/swift/Sources/PackRat/Network/APIClient.swift +++ b/apps/swift/Sources/PackRat/Network/APIClient.swift @@ -206,10 +206,14 @@ actor APIClient { guard let http = response as? HTTPURLResponse else { throw PackRatError.unknown } switch http.statusCode { case 200...299: return - case 401: throw PackRatError.unauthorized + case 401: + if let message = APIErrorBody.decodeMessage(from: data), !message.isEmpty { + throw PackRatError.httpError(statusCode: http.statusCode, message: message) + } + throw PackRatError.unauthorized case 404: throw PackRatError.notFound default: - let message = (try? JSONDecoder().decode(APIErrorBody.self, from: data))?.error + let message = APIErrorBody.decodeMessage(from: data) throw PackRatError.httpError(statusCode: http.statusCode, message: message) } } @@ -231,4 +235,14 @@ actor APIClient { private struct APIErrorBody: Decodable { let error: String? + let message: String? + let code: String? + + static func decodeMessage(from data: Data) -> String? { + guard let body = try? JSONDecoder().decode(Self.self, from: data) else { + return nil + } + + return body.message ?? body.error ?? body.code + } } diff --git a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift index 1d99bd2905..1c4a6c0559 100644 --- a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift +++ b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift @@ -134,9 +134,10 @@ class AppUITestCase: XCTestCase { submitLoginForm() + let loggedIn = waitForLoggedIn(timeout: 20) XCTAssertTrue( - waitForLoggedIn(timeout: 20), - "Logged-in landmark must appear after login — check credentials or network" + loggedIn, + "Logged-in landmark must appear after login — \(visibleLoginFailureMessage())" ) } @@ -184,6 +185,20 @@ class AppUITestCase: XCTestCase { #endif } + func visibleLoginFailureMessage() -> String { + let loginError = app.staticTexts["login_error"] + if loginError.exists, !loginError.label.isEmpty { + return "visible login error: \(loginError.label)" + } + + let inlineError = app.staticTexts["inline_error"] + if inlineError.exists, !inlineError.label.isEmpty { + return "visible inline error: \(inlineError.label)" + } + + return "no visible login error" + } + /// Cross-platform "is the user logged in NOW?" wait with an explicit timeout. /// Distinct from `isLoggedIn` (which has a short fixed wait) so login flow /// can wait longer than the warm-cache short-circuit check. diff --git a/apps/swift/Tests/PackRatUITests/AuthTests.swift b/apps/swift/Tests/PackRatUITests/AuthTests.swift index 52045c2f7c..6da99ac89d 100644 --- a/apps/swift/Tests/PackRatUITests/AuthTests.swift +++ b/apps/swift/Tests/PackRatUITests/AuthTests.swift @@ -231,7 +231,11 @@ final class AuthTests: AppUITestCase { submitLoginForm() - XCTAssertTrue(waitForLoggedIn(timeout: 20), "Logged-in landmark must appear after successful login") + let loggedIn = waitForLoggedIn(timeout: 20) + XCTAssertTrue( + loggedIn, + "Logged-in landmark must appear after successful login — \(visibleLoginFailureMessage())" + ) XCTAssertFalse(app.textFields["login_email"].exists, "Login form should be dismissed") } From 2f46c27b03b8f24fdcf88152fcd2e389365ede2c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 16:30:24 -0600 Subject: [PATCH 48/81] =?UTF-8?q?=F0=9F=91=B7=20preflight=20Swift=20deploy?= =?UTF-8?q?ed=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 8 ++ .../__tests__/verify-deployed-auth.test.ts | 127 ++++++++++++++++++ apps/swift/scripts/verify-deployed-auth.ts | 89 ++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 apps/swift/scripts/__tests__/verify-deployed-auth.test.ts create mode 100644 apps/swift/scripts/verify-deployed-auth.ts diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index cbcb1a61ca..b0edbd0419 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -167,6 +167,10 @@ jobs: E2E_TEST_EMAIL: ${{ env.E2E_EMAIL }} E2E_TEST_PASSWORD: ${{ env.E2E_PASSWORD }} + - name: Verify deployed Swift auth + if: ${{ (inputs.api_environment || 'configured-secret') != 'local' }} + run: bun run apps/swift/scripts/verify-deployed-auth.ts + - name: Run macOS Swift UI E2E run: | if [ "${{ github.event_name }}" = "pull_request" ]; then @@ -295,6 +299,10 @@ jobs: E2E_TEST_EMAIL: ${{ env.E2E_EMAIL }} E2E_TEST_PASSWORD: ${{ env.E2E_PASSWORD }} + - name: Verify deployed Swift auth + if: ${{ (inputs.api_environment || 'configured-secret') != 'local' }} + run: bun run apps/swift/scripts/verify-deployed-auth.ts + - name: Boot iOS simulator run: | DEVICE_ID=$(xcrun simctl create Swift-E2E "iPhone 17" \ diff --git a/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts b/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts new file mode 100644 index 0000000000..62add9cd3f --- /dev/null +++ b/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, it, vi } from 'vitest'; +import { verifyDeployedAuth } from '../verify-deployed-auth'; + +function response(input: { + ok: boolean; + status?: number; + body?: unknown; + tokenHeader?: string; + jsonThrows?: boolean; +}) { + return { + ok: input.ok, + status: input.status ?? (input.ok ? 200 : 401), + headers: new Headers(input.tokenHeader ? { 'set-auth-token': input.tokenHeader } : {}), + json: async () => { + if (input.jsonThrows) throw new Error('not json'); + return input.body ?? {}; + }, + }; +} + +describe('verifyDeployedAuth', () => { + it('posts Better Auth credentials to the deployed API', async () => { + const fetchImpl = vi.fn(async () => + response({ + ok: true, + body: { user: { id: 'user-1' } }, + tokenHeader: 'session-token', + }), + ); + + await verifyDeployedAuth( + { + apiBaseURL: 'https://api.example.test/base-path', + email: 'tester@example.com', + password: 'correct horse battery staple', + }, + fetchImpl, + ); + + expect(fetchImpl).toHaveBeenCalledOnce(); + const [url, init] = fetchImpl.mock.calls[0]; + expect(String(url)).toBe('https://api.example.test/base-path/api/auth/sign-in/email'); + expect(init?.method).toBe('POST'); + expect(init?.headers).toMatchObject({ + 'Content-Type': 'application/json', + Origin: 'packrat://', + }); + expect(JSON.parse(String(init?.body))).toEqual({ + email: 'tester@example.com', + password: 'correct horse battery staple', + }); + }); + + it('accepts a token in the JSON body when the auth header is absent', async () => { + await expect( + verifyDeployedAuth( + { + apiBaseURL: 'https://api.example.test', + email: 'tester@example.com', + password: 'password', + }, + async () => response({ ok: true, body: { user: { id: 'user-1' }, token: 'body-token' } }), + ), + ).resolves.toBeUndefined(); + }); + + it('reports the Better Auth message for bad credentials', async () => { + await expect( + verifyDeployedAuth( + { + apiBaseURL: 'https://api.example.test', + email: 'tester@example.com', + password: 'wrong', + }, + async () => + response({ + ok: false, + status: 401, + body: { message: 'Invalid email or password', code: 'INVALID_EMAIL_OR_PASSWORD' }, + }), + ), + ).rejects.toThrow('Swift deployed auth preflight failed: Invalid email or password'); + }); + + it('falls back to status when the error body is not JSON', async () => { + await expect( + verifyDeployedAuth( + { + apiBaseURL: 'https://api.example.test', + email: 'tester@example.com', + password: 'wrong', + }, + async () => response({ ok: false, status: 503, jsonThrows: true }), + ), + ).rejects.toThrow('Swift deployed auth preflight failed: HTTP 503'); + }); + + it('fails when the deployed response lacks a user or session token', async () => { + await expect( + verifyDeployedAuth( + { + apiBaseURL: 'https://api.example.test', + email: 'tester@example.com', + password: 'password', + }, + async () => response({ ok: true, body: { user: { id: 'user-1' } } }), + ), + ).rejects.toThrow('Swift deployed auth preflight succeeded without user or session token'); + }); + + it('fails before network calls when inputs are missing', async () => { + const fetchImpl = vi.fn(); + + await expect( + verifyDeployedAuth( + { + apiBaseURL: 'https://api.example.test', + email: '', + password: 'password', + }, + fetchImpl, + ), + ).rejects.toThrow('Missing deployed auth preflight input: E2E_EMAIL'); + expect(fetchImpl).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/swift/scripts/verify-deployed-auth.ts b/apps/swift/scripts/verify-deployed-auth.ts new file mode 100644 index 0000000000..c7e79930c6 --- /dev/null +++ b/apps/swift/scripts/verify-deployed-auth.ts @@ -0,0 +1,89 @@ +type AuthPreflightInput = { + apiBaseURL: string | undefined; + email: string | undefined; + password: string | undefined; +}; + +type FetchLike = ( + input: string | URL, + init?: RequestInit, +) => Promise>; + +type AuthResponseBody = { + user?: unknown; + token?: unknown; + message?: unknown; + error?: unknown; + code?: unknown; +}; + +function required(value: string | undefined, name: string): string { + const trimmed = value?.trim(); + if (!trimmed) throw new Error(`Missing deployed auth preflight input: ${name}`); + return trimmed; +} + +function responseMessage(body: AuthResponseBody, status: number): string { + for (const value of [body.message, body.error, body.code]) { + if (typeof value === 'string' && value.trim()) return value.trim(); + } + return `HTTP ${status}`; +} + +async function parseBody(response: Pick): Promise { + try { + const body = await response.json(); + return body && typeof body === 'object' ? (body as AuthResponseBody) : {}; + } catch { + return {}; + } +} + +function signInURL(apiBaseURL: string): URL { + const normalizedBaseURL = apiBaseURL.endsWith('/') ? apiBaseURL : `${apiBaseURL}/`; + return new URL('api/auth/sign-in/email', normalizedBaseURL); +} + +export async function verifyDeployedAuth( + input: AuthPreflightInput, + fetchImpl: FetchLike = fetch, +): Promise { + const apiBaseURL = required(input.apiBaseURL, 'E2E_API_BASE_URL'); + const email = required(input.email, 'E2E_EMAIL'); + const password = required(input.password, 'E2E_PASSWORD'); + + const response = await fetchImpl(signInURL(apiBaseURL), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Origin: 'packrat://', + }, + body: JSON.stringify({ email, password }), + }); + const body = await parseBody(response); + const token = response.headers.get('set-auth-token') ?? body.token; + + if (!response.ok) { + throw new Error( + `Swift deployed auth preflight failed: ${responseMessage(body, response.status)}`, + ); + } + + if (!body.user || !token) { + throw new Error('Swift deployed auth preflight succeeded without user or session token'); + } +} + +if (import.meta.main) { + try { + await verifyDeployedAuth({ + apiBaseURL: process.env.E2E_API_BASE_URL, + email: process.env.E2E_EMAIL, + password: process.env.E2E_PASSWORD, + }); + console.log('Swift deployed auth preflight passed'); + } catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} From 5a629585affccc94b0d38d302d421cb25aeea152 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 16:33:06 -0600 Subject: [PATCH 49/81] =?UTF-8?q?=F0=9F=94=A7=20route=20Swift=20auth=20pre?= =?UTF-8?q?flight=20env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/verify-deployed-auth.test.ts | 23 ++++++------- apps/swift/scripts/verify-deployed-auth.ts | 32 +++++++++++-------- packages/env/src/node.ts | 2 ++ 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts b/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts index 62add9cd3f..dc30ff909c 100644 --- a/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts +++ b/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts @@ -34,8 +34,8 @@ describe('verifyDeployedAuth', () => { apiBaseURL: 'https://api.example.test/base-path', email: 'tester@example.com', password: 'correct horse battery staple', + fetchImpl, }, - fetchImpl, ); expect(fetchImpl).toHaveBeenCalledOnce(); @@ -59,8 +59,9 @@ describe('verifyDeployedAuth', () => { apiBaseURL: 'https://api.example.test', email: 'tester@example.com', password: 'password', + fetchImpl: async () => + response({ ok: true, body: { user: { id: 'user-1' }, token: 'body-token' } }), }, - async () => response({ ok: true, body: { user: { id: 'user-1' }, token: 'body-token' } }), ), ).resolves.toBeUndefined(); }); @@ -72,13 +73,13 @@ describe('verifyDeployedAuth', () => { apiBaseURL: 'https://api.example.test', email: 'tester@example.com', password: 'wrong', + fetchImpl: async () => + response({ + ok: false, + status: 401, + body: { message: 'Invalid email or password', code: 'INVALID_EMAIL_OR_PASSWORD' }, + }), }, - async () => - response({ - ok: false, - status: 401, - body: { message: 'Invalid email or password', code: 'INVALID_EMAIL_OR_PASSWORD' }, - }), ), ).rejects.toThrow('Swift deployed auth preflight failed: Invalid email or password'); }); @@ -90,8 +91,8 @@ describe('verifyDeployedAuth', () => { apiBaseURL: 'https://api.example.test', email: 'tester@example.com', password: 'wrong', + fetchImpl: async () => response({ ok: false, status: 503, jsonThrows: true }), }, - async () => response({ ok: false, status: 503, jsonThrows: true }), ), ).rejects.toThrow('Swift deployed auth preflight failed: HTTP 503'); }); @@ -103,8 +104,8 @@ describe('verifyDeployedAuth', () => { apiBaseURL: 'https://api.example.test', email: 'tester@example.com', password: 'password', + fetchImpl: async () => response({ ok: true, body: { user: { id: 'user-1' } } }), }, - async () => response({ ok: true, body: { user: { id: 'user-1' } } }), ), ).rejects.toThrow('Swift deployed auth preflight succeeded without user or session token'); }); @@ -118,8 +119,8 @@ describe('verifyDeployedAuth', () => { apiBaseURL: 'https://api.example.test', email: '', password: 'password', + fetchImpl, }, - fetchImpl, ), ).rejects.toThrow('Missing deployed auth preflight input: E2E_EMAIL'); expect(fetchImpl).not.toHaveBeenCalled(); diff --git a/apps/swift/scripts/verify-deployed-auth.ts b/apps/swift/scripts/verify-deployed-auth.ts index c7e79930c6..f9c95446c9 100644 --- a/apps/swift/scripts/verify-deployed-auth.ts +++ b/apps/swift/scripts/verify-deployed-auth.ts @@ -1,7 +1,10 @@ +import { nodeEnv } from '@packrat/env/node'; + type AuthPreflightInput = { apiBaseURL: string | undefined; email: string | undefined; password: string | undefined; + fetchImpl?: FetchLike; }; type FetchLike = ( @@ -17,13 +20,15 @@ type AuthResponseBody = { code?: unknown; }; -function required(value: string | undefined, name: string): string { +function required(input: { value: string | undefined; name: string }): string { + const { value, name } = input; const trimmed = value?.trim(); if (!trimmed) throw new Error(`Missing deployed auth preflight input: ${name}`); return trimmed; } -function responseMessage(body: AuthResponseBody, status: number): string { +function responseMessage(input: { body: AuthResponseBody; status: number }): string { + const { body, status } = input; for (const value of [body.message, body.error, body.code]) { if (typeof value === 'string' && value.trim()) return value.trim(); } @@ -44,13 +49,11 @@ function signInURL(apiBaseURL: string): URL { return new URL('api/auth/sign-in/email', normalizedBaseURL); } -export async function verifyDeployedAuth( - input: AuthPreflightInput, - fetchImpl: FetchLike = fetch, -): Promise { - const apiBaseURL = required(input.apiBaseURL, 'E2E_API_BASE_URL'); - const email = required(input.email, 'E2E_EMAIL'); - const password = required(input.password, 'E2E_PASSWORD'); +export async function verifyDeployedAuth(input: AuthPreflightInput): Promise { + const fetchImpl = input.fetchImpl ?? fetch; + const apiBaseURL = required({ value: input.apiBaseURL, name: 'E2E_API_BASE_URL' }); + const email = required({ value: input.email, name: 'E2E_EMAIL' }); + const password = required({ value: input.password, name: 'E2E_PASSWORD' }); const response = await fetchImpl(signInURL(apiBaseURL), { method: 'POST', @@ -65,7 +68,10 @@ export async function verifyDeployedAuth( if (!response.ok) { throw new Error( - `Swift deployed auth preflight failed: ${responseMessage(body, response.status)}`, + `Swift deployed auth preflight failed: ${responseMessage({ + body, + status: response.status, + })}`, ); } @@ -77,9 +83,9 @@ export async function verifyDeployedAuth( if (import.meta.main) { try { await verifyDeployedAuth({ - apiBaseURL: process.env.E2E_API_BASE_URL, - email: process.env.E2E_EMAIL, - password: process.env.E2E_PASSWORD, + apiBaseURL: nodeEnv.E2E_API_BASE_URL, + email: nodeEnv.E2E_EMAIL, + password: nodeEnv.E2E_PASSWORD, }); console.log('Swift deployed auth preflight passed'); } catch (error) { diff --git a/packages/env/src/node.ts b/packages/env/src/node.ts index 9ddb9007b7..d58fe759b6 100644 --- a/packages/env/src/node.ts +++ b/packages/env/src/node.ts @@ -84,6 +84,7 @@ export const nodeEnvSchema = z.object({ E2E_PASSWORD: z.string().min(1).optional(), E2E_TEST_EMAIL: z.string().email().optional(), E2E_TEST_PASSWORD: z.string().min(1).optional(), + E2E_API_BASE_URL: z.string().url().optional(), // ── OpenAI (packages/api/src/db/seed-e2e-catalog.ts) ────────────── OPENAI_API_KEY: z.string().min(1).optional(), @@ -165,6 +166,7 @@ export const nodeEnv = nodeEnvSchema.parse({ E2E_PASSWORD: process.env.E2E_PASSWORD, E2E_TEST_EMAIL: process.env.E2E_TEST_EMAIL, E2E_TEST_PASSWORD: process.env.E2E_TEST_PASSWORD, + E2E_API_BASE_URL: process.env.E2E_API_BASE_URL, OPENAI_API_KEY: process.env.OPENAI_API_KEY, WEATHER_API_KEY: process.env.WEATHER_API_KEY, APPLE_PRIVATE_KEY: process.env.APPLE_PRIVATE_KEY, From d01463689f85031046f3cd5d2f5c1dbb4a6225da Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 16:35:02 -0600 Subject: [PATCH 50/81] =?UTF-8?q?=F0=9F=8E=A8=20format=20Swift=20auth=20pr?= =?UTF-8?q?eflight=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/verify-deployed-auth.test.ts | 96 ++++++++----------- 1 file changed, 42 insertions(+), 54 deletions(-) diff --git a/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts b/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts index dc30ff909c..b0644a3411 100644 --- a/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts +++ b/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts @@ -29,14 +29,12 @@ describe('verifyDeployedAuth', () => { }), ); - await verifyDeployedAuth( - { - apiBaseURL: 'https://api.example.test/base-path', - email: 'tester@example.com', - password: 'correct horse battery staple', - fetchImpl, - }, - ); + await verifyDeployedAuth({ + apiBaseURL: 'https://api.example.test/base-path', + email: 'tester@example.com', + password: 'correct horse battery staple', + fetchImpl, + }); expect(fetchImpl).toHaveBeenCalledOnce(); const [url, init] = fetchImpl.mock.calls[0]; @@ -54,59 +52,51 @@ describe('verifyDeployedAuth', () => { it('accepts a token in the JSON body when the auth header is absent', async () => { await expect( - verifyDeployedAuth( - { - apiBaseURL: 'https://api.example.test', - email: 'tester@example.com', - password: 'password', - fetchImpl: async () => - response({ ok: true, body: { user: { id: 'user-1' }, token: 'body-token' } }), - }, - ), + verifyDeployedAuth({ + apiBaseURL: 'https://api.example.test', + email: 'tester@example.com', + password: 'password', + fetchImpl: async () => + response({ ok: true, body: { user: { id: 'user-1' }, token: 'body-token' } }), + }), ).resolves.toBeUndefined(); }); it('reports the Better Auth message for bad credentials', async () => { await expect( - verifyDeployedAuth( - { - apiBaseURL: 'https://api.example.test', - email: 'tester@example.com', - password: 'wrong', - fetchImpl: async () => - response({ - ok: false, - status: 401, - body: { message: 'Invalid email or password', code: 'INVALID_EMAIL_OR_PASSWORD' }, - }), - }, - ), + verifyDeployedAuth({ + apiBaseURL: 'https://api.example.test', + email: 'tester@example.com', + password: 'wrong', + fetchImpl: async () => + response({ + ok: false, + status: 401, + body: { message: 'Invalid email or password', code: 'INVALID_EMAIL_OR_PASSWORD' }, + }), + }), ).rejects.toThrow('Swift deployed auth preflight failed: Invalid email or password'); }); it('falls back to status when the error body is not JSON', async () => { await expect( - verifyDeployedAuth( - { - apiBaseURL: 'https://api.example.test', - email: 'tester@example.com', - password: 'wrong', - fetchImpl: async () => response({ ok: false, status: 503, jsonThrows: true }), - }, - ), + verifyDeployedAuth({ + apiBaseURL: 'https://api.example.test', + email: 'tester@example.com', + password: 'wrong', + fetchImpl: async () => response({ ok: false, status: 503, jsonThrows: true }), + }), ).rejects.toThrow('Swift deployed auth preflight failed: HTTP 503'); }); it('fails when the deployed response lacks a user or session token', async () => { await expect( - verifyDeployedAuth( - { - apiBaseURL: 'https://api.example.test', - email: 'tester@example.com', - password: 'password', - fetchImpl: async () => response({ ok: true, body: { user: { id: 'user-1' } } }), - }, - ), + verifyDeployedAuth({ + apiBaseURL: 'https://api.example.test', + email: 'tester@example.com', + password: 'password', + fetchImpl: async () => response({ ok: true, body: { user: { id: 'user-1' } } }), + }), ).rejects.toThrow('Swift deployed auth preflight succeeded without user or session token'); }); @@ -114,14 +104,12 @@ describe('verifyDeployedAuth', () => { const fetchImpl = vi.fn(); await expect( - verifyDeployedAuth( - { - apiBaseURL: 'https://api.example.test', - email: '', - password: 'password', - fetchImpl, - }, - ), + verifyDeployedAuth({ + apiBaseURL: 'https://api.example.test', + email: '', + password: 'password', + fetchImpl, + }), ).rejects.toThrow('Missing deployed auth preflight input: E2E_EMAIL'); expect(fetchImpl).not.toHaveBeenCalled(); }); From 87736949e95652b95eb8dd7e9c61363708bee94a Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 16:36:42 -0600 Subject: [PATCH 51/81] =?UTF-8?q?=F0=9F=A7=B9=20satisfy=20Swift=20auth=20p?= =?UTF-8?q?reflight=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/scripts/verify-deployed-auth.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/swift/scripts/verify-deployed-auth.ts b/apps/swift/scripts/verify-deployed-auth.ts index f9c95446c9..54f6da8a82 100644 --- a/apps/swift/scripts/verify-deployed-auth.ts +++ b/apps/swift/scripts/verify-deployed-auth.ts @@ -1,4 +1,6 @@ import { nodeEnv } from '@packrat/env/node'; +import { isObject, isString } from '@packrat/guards'; +import { safeJsonStringify } from '@packrat/utils'; type AuthPreflightInput = { apiBaseURL: string | undefined; @@ -30,7 +32,7 @@ function required(input: { value: string | undefined; name: string }): string { function responseMessage(input: { body: AuthResponseBody; status: number }): string { const { body, status } = input; for (const value of [body.message, body.error, body.code]) { - if (typeof value === 'string' && value.trim()) return value.trim(); + if (isString(value) && value.trim()) return value.trim(); } return `HTTP ${status}`; } @@ -38,7 +40,7 @@ function responseMessage(input: { body: AuthResponseBody; status: number }): str async function parseBody(response: Pick): Promise { try { const body = await response.json(); - return body && typeof body === 'object' ? (body as AuthResponseBody) : {}; + return isObject(body) ? (body as AuthResponseBody) : {}; } catch { return {}; } @@ -61,7 +63,7 @@ export async function verifyDeployedAuth(input: AuthPreflightInput): Promise Date: Sat, 18 Jul 2026 16:38:05 -0600 Subject: [PATCH 52/81] =?UTF-8?q?=F0=9F=A7=B9=20remove=20Swift=20auth=20pr?= =?UTF-8?q?eflight=20cast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/scripts/verify-deployed-auth.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/swift/scripts/verify-deployed-auth.ts b/apps/swift/scripts/verify-deployed-auth.ts index 54f6da8a82..f564091bff 100644 --- a/apps/swift/scripts/verify-deployed-auth.ts +++ b/apps/swift/scripts/verify-deployed-auth.ts @@ -14,7 +14,7 @@ type FetchLike = ( init?: RequestInit, ) => Promise>; -type AuthResponseBody = { +type AuthResponseBody = Record & { user?: unknown; token?: unknown; message?: unknown; @@ -40,7 +40,14 @@ function responseMessage(input: { body: AuthResponseBody; status: number }): str async function parseBody(response: Pick): Promise { try { const body = await response.json(); - return isObject(body) ? (body as AuthResponseBody) : {}; + if (!isObject(body)) return {}; + return { + user: body.user, + token: body.token, + message: body.message, + error: body.error, + code: body.code, + }; } catch { return {}; } From e659327174e0930c91202461a04f9d9b15c2aff2 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:03:36 -0600 Subject: [PATCH 53/81] =?UTF-8?q?=F0=9F=A7=AA=20harden=20Swift=20login=20s?= =?UTF-8?q?ubmit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Tests/PackRatUITests/AppUITestCase.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift index 1c4a6c0559..49a51590b0 100644 --- a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift +++ b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift @@ -171,8 +171,6 @@ class AppUITestCase: XCTestCase { app.typeText("\u{1b}") submitButton.tap() #else - app.typeText("\n") - Thread.sleep(forTimeInterval: 0.5) if submitButton.isHittable { submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() Thread.sleep(forTimeInterval: 0.25) @@ -180,7 +178,15 @@ class AppUITestCase: XCTestCase { submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } } else { - app.typeText("\n") + let goButton = app.keyboards.buttons["Go"] + let returnButton = app.keyboards.buttons["Return"] + if goButton.waitForExistence(timeout: 1) { + goButton.tap() + } else if returnButton.waitForExistence(timeout: 1) { + returnButton.tap() + } else { + submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + } } #endif } From 3108b38d5ff8c3b3003b8b94d13c1bc8b7a3130b Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:32:36 -0600 Subject: [PATCH 54/81] =?UTF-8?q?=F0=9F=9A=A6=20gate=20Swift=20replacement?= =?UTF-8?q?=20TestFlight=20uploads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/README.md | 8 ++ .../__tests__/testflight-config.test.ts | 63 +++++++++++++++ .../__tests__/testflight-upload-cli.test.ts | 25 ++++++ apps/swift/scripts/lib/testflight-config.ts | 79 +++++++++++++++++++ .../scripts/verify-testflight-replacement.ts | 70 ++++++++++++++++ package.json | 1 + packages/env/src/node.ts | 2 + 7 files changed, 248 insertions(+) create mode 100644 apps/swift/scripts/verify-testflight-replacement.ts diff --git a/apps/swift/README.md b/apps/swift/README.md index 3e3123e3d8..bf82403dc6 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -94,6 +94,8 @@ Upload commands require an explicit lane so we do not accidentally test the wrong App Store Connect record: ```sh +APP_STORE_CURRENT_BUILD_NUMBER=2026071801 BUILD_NUMBER=2026071802 \ + bun swift:testflight:preflight --replacement --production bun apps/swift/scripts/upload-testflight.ts --replacement --dry-run bun apps/swift/scripts/upload-testflight.ts --replacement bun apps/swift/scripts/upload-testflight.ts --side-by-side --staging @@ -106,6 +108,12 @@ Use `--dry-run` before a real upload to verify the lane, bundle id, display name, build configuration, API environment, and Xcode archive overrides without requiring Apple credentials or running Xcode. +Use `swift:testflight:preflight` before the replacement upload when validating a +seamless update. It fails unless the resolved archive is the existing Expo +listing (`com.andrewbierman.packrat`, `PackRat`), Release/production, and, when +`APP_STORE_CURRENT_BUILD_NUMBER` is supplied, has a strictly greater build +number than the latest App Store Connect build. + ## Data Isolation Swift E2E tests use unique names for records they create. That keeps repeated diff --git a/apps/swift/scripts/__tests__/testflight-config.test.ts b/apps/swift/scripts/__tests__/testflight-config.test.ts index 3e19f99be6..0588f0c72c 100644 --- a/apps/swift/scripts/__tests__/testflight-config.test.ts +++ b/apps/swift/scripts/__tests__/testflight-config.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'; import { parseTestFlightUploadConfig, TestFlightConfigError, + verifyTestFlightReplacementReadiness, xcodeArchiveOverrides, } from '../lib/testflight-config'; @@ -112,4 +113,66 @@ describe('parseTestFlightUploadConfig', () => { parseTestFlightUploadConfig({ argv: ['--replacement', '--staging', '--production'] }), ).toThrow('Use either --staging or --production, not both.'); }); + + it('verifies replacement settings for seamless TestFlight update', () => { + const config = parseTestFlightUploadConfig({ + argv: ['--replacement', '--production'], + env: { BUILD_NUMBER: '2026071802' }, + }); + + expect( + verifyTestFlightReplacementReadiness({ + config, + currentAppStoreBuildNumber: '2026071801', + }), + ).toEqual({ ok: true, errors: [], warnings: [] }); + }); + + it('rejects side-by-side settings for replacement readiness', () => { + const config = parseTestFlightUploadConfig({ + argv: ['--side-by-side', '--production'], + env: { BUILD_NUMBER: '2026071802' }, + }); + + const readiness = verifyTestFlightReplacementReadiness({ + config, + currentAppStoreBuildNumber: '2026071801', + }); + + expect(readiness.ok).toBe(false); + expect(readiness.errors).toContain( + 'Use --replacement. Side-by-side Swift beta builds cannot update the Expo app.', + ); + }); + + it('rejects stale replacement build numbers', () => { + const config = parseTestFlightUploadConfig({ + argv: ['--replacement', '--production'], + env: { BUILD_NUMBER: '2026071801' }, + }); + + const readiness = verifyTestFlightReplacementReadiness({ + config, + currentAppStoreBuildNumber: '2026071801', + }); + + expect(readiness.ok).toBe(false); + expect(readiness.errors).toContain( + 'Build number 2026071801 must be greater than current App Store/TestFlight build 2026071801.', + ); + }); + + it('warns when current App Store build is not supplied', () => { + const config = parseTestFlightUploadConfig({ + argv: ['--replacement', '--production'], + env: { BUILD_NUMBER: '2026071802' }, + }); + + const readiness = verifyTestFlightReplacementReadiness({ config }); + + expect(readiness.ok).toBe(true); + expect(readiness.warnings).toContain( + 'APP_STORE_CURRENT_BUILD_NUMBER was not provided; verify the replacement build number is greater than the latest App Store Connect build before upload.', + ); + }); }); diff --git a/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts index 2aa4eb60f6..42be81b1e7 100644 --- a/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts +++ b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from 'vitest'; const repoRoot = resolve(__dirname, '../../../..'); const script = resolve(repoRoot, 'apps/swift/scripts/upload-testflight.ts'); +const verifyScript = resolve(repoRoot, 'apps/swift/scripts/verify-testflight-replacement.ts'); describe('upload-testflight CLI', () => { it('uses BUILD_NUMBER in dry-run preflight output', () => { @@ -25,4 +26,28 @@ describe('upload-testflight CLI', () => { }); expect(preflight.archiveOverrides).toContain('CURRENT_PROJECT_VERSION=2026071801'); }); + + it('verifies replacement TestFlight readiness from the CLI', () => { + const output = execFileSync('bun', [verifyScript, '--replacement', '--production'], { + cwd: repoRoot, + encoding: 'utf8', + env: { + ...currentEnv, + BUILD_NUMBER: '2026071802', + APP_STORE_CURRENT_BUILD_NUMBER: '2026071801', + }, + }); + + const report = JSON.parse(output); + expect(report).toMatchObject({ + lane: 'replacement', + bundleId: 'com.andrewbierman.packrat', + displayName: 'PackRat', + apiEnvironment: 'production', + buildNumber: '2026071802', + currentAppStoreBuildNumber: '2026071801', + ok: true, + errors: [], + }); + }); }); diff --git a/apps/swift/scripts/lib/testflight-config.ts b/apps/swift/scripts/lib/testflight-config.ts index a05a711bcc..c26002f257 100644 --- a/apps/swift/scripts/lib/testflight-config.ts +++ b/apps/swift/scripts/lib/testflight-config.ts @@ -14,6 +14,17 @@ export type TestFlightUploadConfig = { apiEnvironment: 'dev' | 'production'; }; +export type TestFlightReplacementReadinessInput = { + config: TestFlightUploadConfig; + currentAppStoreBuildNumber?: string | undefined; +}; + +export type TestFlightReplacementReadiness = { + ok: boolean; + errors: string[]; + warnings: string[]; +}; + export class TestFlightConfigError extends Error { constructor(message: string) { super(message); @@ -78,3 +89,71 @@ export function xcodeArchiveOverrides(input: { `PACKRAT_DISPLAY_NAME=${config.displayName}`, ]; } + +function isPositiveInteger(value: string): boolean { + const numeric = Number(value); + return Number.isSafeInteger(numeric) && numeric > 0 && String(numeric) === value; +} + +export function verifyTestFlightReplacementReadiness( + input: TestFlightReplacementReadinessInput, +): TestFlightReplacementReadiness { + const { config, currentAppStoreBuildNumber } = input; + const errors: string[] = []; + const warnings: string[] = []; + + if (config.lane !== 'replacement') { + errors.push('Use --replacement. Side-by-side Swift beta builds cannot update the Expo app.'); + } + if ( + config.staging || + config.configuration !== 'Release' || + config.apiEnvironment !== 'production' + ) { + errors.push('Use the production Release archive for a seamless TestFlight update.'); + } + if (config.bundleId !== REPLACEMENT_BUNDLE_ID) { + errors.push(`Expected iOS bundle id ${REPLACEMENT_BUNDLE_ID}, got ${config.bundleId}.`); + } + if (config.watchBundleId !== REPLACEMENT_WATCH_BUNDLE_ID) { + errors.push( + `Expected watch bundle id ${REPLACEMENT_WATCH_BUNDLE_ID}, got ${config.watchBundleId}.`, + ); + } + if (config.companionBundleId !== REPLACEMENT_BUNDLE_ID) { + errors.push( + `Expected watch companion bundle id ${REPLACEMENT_BUNDLE_ID}, got ${config.companionBundleId}.`, + ); + } + if (config.displayName !== 'PackRat') { + errors.push(`Expected display name PackRat, got ${config.displayName}.`); + } + if (!isPositiveInteger(config.buildNumber)) { + errors.push(`Build number must be a positive integer, got ${config.buildNumber}.`); + } + + if (currentAppStoreBuildNumber) { + if (!isPositiveInteger(currentAppStoreBuildNumber)) { + errors.push( + `Current App Store build number must be a positive integer, got ${currentAppStoreBuildNumber}.`, + ); + } else if ( + isPositiveInteger(config.buildNumber) && + Number(config.buildNumber) <= Number(currentAppStoreBuildNumber) + ) { + errors.push( + `Build number ${config.buildNumber} must be greater than current App Store/TestFlight build ${currentAppStoreBuildNumber}.`, + ); + } + } else { + warnings.push( + 'APP_STORE_CURRENT_BUILD_NUMBER was not provided; verify the replacement build number is greater than the latest App Store Connect build before upload.', + ); + } + + return { + ok: errors.length === 0, + errors, + warnings, + }; +} diff --git a/apps/swift/scripts/verify-testflight-replacement.ts b/apps/swift/scripts/verify-testflight-replacement.ts new file mode 100644 index 0000000000..823cda4ed1 --- /dev/null +++ b/apps/swift/scripts/verify-testflight-replacement.ts @@ -0,0 +1,70 @@ +#!/usr/bin/env bun +/** + * Verifies that the Swift TestFlight upload settings are compatible with a + * seamless update of the existing Expo iOS listing. + * + * This is a preflight gate only; it does not archive or upload. + * + * Usage: + * APP_STORE_CURRENT_BUILD_NUMBER=123 BUILD_NUMBER=456 \ + * bun apps/swift/scripts/verify-testflight-replacement.ts --replacement --production + */ +import { nodeEnv } from '@packrat/env/node'; +import { safeJsonStringify } from '@packrat/utils'; +import { + parseTestFlightUploadConfig, + TestFlightConfigError, + verifyTestFlightReplacementReadiness, +} from './lib/testflight-config'; + +const HELP = process.argv.includes('--help') || process.argv.includes('-h'); + +function usage(): string { + return [ + 'Usage:', + ' bun apps/swift/scripts/verify-testflight-replacement.ts --replacement --production', + '', + 'Recommended env:', + ' BUILD_NUMBER build number intended for upload', + ' APP_STORE_CURRENT_BUILD_NUMBER latest existing PackRat App Store/TestFlight build', + ].join('\n'); +} + +if (HELP) { + console.log(usage()); + process.exit(0); +} + +try { + const config = parseTestFlightUploadConfig({ + argv: process.argv.slice(2), + env: { BUILD_NUMBER: nodeEnv.BUILD_NUMBER }, + }); + const readiness = verifyTestFlightReplacementReadiness({ + config, + currentAppStoreBuildNumber: nodeEnv.APP_STORE_CURRENT_BUILD_NUMBER, + }); + const report = { + lane: config.lane, + bundleId: config.bundleId, + watchBundleId: config.watchBundleId, + companionBundleId: config.companionBundleId, + displayName: config.displayName, + configuration: config.configuration, + apiEnvironment: config.apiEnvironment, + buildNumber: config.buildNumber, + currentAppStoreBuildNumber: nodeEnv.APP_STORE_CURRENT_BUILD_NUMBER ?? null, + ok: readiness.ok, + errors: readiness.errors, + warnings: readiness.warnings, + }; + + console.log(safeJsonStringify(report, null, 2)); + if (!readiness.ok) process.exit(1); +} catch (error) { + if (error instanceof TestFlightConfigError) { + console.error(`${error.message}\n\n${usage()}`); + process.exit(1); + } + throw error; +} diff --git a/package.json b/package.json index a2deb0be89..c97e66904d 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "swift:models": "bun run apps/swift/scripts/generate-swift-models.ts", "swift:quicktype": "bun run apps/swift/scripts/generate-quicktype-models.ts", "swift:screenshots": "bun run apps/swift/scripts/capture-visual-screenshots.ts", + "swift:testflight:preflight": "bun run apps/swift/scripts/verify-testflight-replacement.ts", "swift:validate-assets": "bun run apps/swift/scripts/validate-app-store-assets.ts", "swift:watch-sync-smoke": "bun run apps/swift/scripts/watch-sync-smoke.ts", "test:api:unit": "vitest run --config packages/api/vitest.unit.config.ts", diff --git a/packages/env/src/node.ts b/packages/env/src/node.ts index d58fe759b6..3b43d52ced 100644 --- a/packages/env/src/node.ts +++ b/packages/env/src/node.ts @@ -123,6 +123,7 @@ export const nodeEnvSchema = z.object({ APPLE_TEAM_ID: z.string().min(1).optional(), APPLE_ASC_PROVIDER: z.string().min(1).optional(), BUILD_NUMBER: z.string().regex(/^\d+$/).optional(), + APP_STORE_CURRENT_BUILD_NUMBER: z.string().regex(/^\d+$/).optional(), }); export type NodeEnv = z.infer; @@ -203,4 +204,5 @@ export const nodeEnv = nodeEnvSchema.parse({ APPLE_TEAM_ID: process.env.APPLE_TEAM_ID, APPLE_ASC_PROVIDER: process.env.APPLE_ASC_PROVIDER, BUILD_NUMBER: process.env.BUILD_NUMBER, + APP_STORE_CURRENT_BUILD_NUMBER: process.env.APP_STORE_CURRENT_BUILD_NUMBER, }); From 086324d4dfd5e1be11a0c3ff53f3a7fe55979b49 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:34:27 -0600 Subject: [PATCH 55/81] =?UTF-8?q?=F0=9F=91=B7=20add=20Swift=20TestFlight?= =?UTF-8?q?=20preflight=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 47 +++++++++++++++++++++++++++++++++ apps/swift/README.md | 5 ++++ 2 files changed, 52 insertions(+) diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index b0edbd0419..6292853b77 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -63,6 +63,19 @@ on: - default - all-on - all-off + run_testflight_preflight: + description: "Verify replacement TestFlight metadata without archiving" + required: false + type: boolean + default: false + replacement_build_number: + description: "Swift replacement build number intended for upload" + required: false + type: string + current_app_store_build_number: + description: "Latest existing PackRat App Store/TestFlight build number" + required: false + type: string concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -80,6 +93,40 @@ env: PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN: ${{ secrets.PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN }} jobs: + testflight-replacement-preflight: + name: TestFlight replacement preflight + runs-on: ubuntu-latest + timeout-minutes: 10 + if: github.event_name == 'workflow_dispatch' && inputs.run_testflight_preflight + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + env: + PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN: ${{ secrets.PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN }} + run: bun install --frozen-lockfile + + - name: Verify replacement TestFlight metadata + env: + BUILD_NUMBER: ${{ inputs.replacement_build_number }} + APP_STORE_CURRENT_BUILD_NUMBER: ${{ inputs.current_app_store_build_number }} + run: | + missing=() + [ -z "${BUILD_NUMBER:-}" ] && missing+=("replacement_build_number") + [ -z "${APP_STORE_CURRENT_BUILD_NUMBER:-}" ] && missing+=("current_app_store_build_number") + if [ ${#missing[@]} -gt 0 ]; then + echo "::error::Required TestFlight preflight inputs missing: ${missing[*]}" + exit 1 + fi + bun swift:testflight:preflight --replacement --production + macos-ui: name: macOS Swift UI E2E runs-on: [self-hosted, macOS, packrat-e2e] diff --git a/apps/swift/README.md b/apps/swift/README.md index bf82403dc6..8d1b473466 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -114,6 +114,11 @@ listing (`com.andrewbierman.packrat`, `PackRat`), Release/production, and, when `APP_STORE_CURRENT_BUILD_NUMBER` is supplied, has a strictly greater build number than the latest App Store Connect build. +The same check is available from the manual **Swift E2E Tests** GitHub workflow: +enable `run_testflight_preflight`, then provide `replacement_build_number` and +`current_app_store_build_number`. That verifies the replacement metadata without +archiving or uploading. + ## Data Isolation Swift E2E tests use unique names for records they create. That keeps repeated From 1f845ab254f3e53c8a7471cf33efa3847655aa15 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:38:07 -0600 Subject: [PATCH 56/81] =?UTF-8?q?=F0=9F=94=97=20route=20Swift=20deep=20lin?= =?UTF-8?q?ks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PackRat/Features/Auth/AuthGateView.swift | 6 ---- .../PackRat/Navigation/AppNavigation.swift | 8 +++++ .../PackRat/Navigation/DeepLinkRouting.swift | 31 +++++++++++++++++++ .../Tests/PackRatTests/DeepLinkTests.swift | 21 +++++++++++++ 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 apps/swift/Sources/PackRat/Navigation/DeepLinkRouting.swift diff --git a/apps/swift/Sources/PackRat/Features/Auth/AuthGateView.swift b/apps/swift/Sources/PackRat/Features/Auth/AuthGateView.swift index 80deddd978..ad60f38d02 100644 --- a/apps/swift/Sources/PackRat/Features/Auth/AuthGateView.swift +++ b/apps/swift/Sources/PackRat/Features/Auth/AuthGateView.swift @@ -29,12 +29,6 @@ struct AuthGateView: View { return } #endif - let link = DeepLink.parse(url) - // Routing per destination is deferred — the scheme handler is wired here - // so deep links surface via Sentry breadcrumbs (once U9 lands) and the - // logs, even before each destination has a binding. This is enough to - // close the parity gap with Expo's `packrat://` scheme. - print("[DeepLink] received \(url) → \(link)") } } diff --git a/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift b/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift index 27fd1b84b9..77132b670b 100644 --- a/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift +++ b/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift @@ -100,6 +100,14 @@ struct AppNavigation: View { #endif var body: some View { + navigationBody + .onOpenURL { url in + appState.apply(DeepLink.parse(url)) + } + } + + @ViewBuilder + private var navigationBody: some View { #if os(iOS) if horizontalSizeClass == .compact { phoneLayout diff --git a/apps/swift/Sources/PackRat/Navigation/DeepLinkRouting.swift b/apps/swift/Sources/PackRat/Navigation/DeepLinkRouting.swift new file mode 100644 index 0000000000..11022b141a --- /dev/null +++ b/apps/swift/Sources/PackRat/Navigation/DeepLinkRouting.swift @@ -0,0 +1,31 @@ +import Foundation + +@MainActor +extension AppState { + @discardableResult + func apply(_ deepLink: DeepLink) -> Bool { + switch deepLink { + case .home: + navItem = .home + return true + case .pack(let id): + navItem = .packs + selectedPackId = id + return true + case .trip(let id): + guard NavItem.trips.isFeatureEnabled else { return false } + navItem = .trips + selectedTripId = id + return true + case .feed: + guard NavItem.feed.isFeatureEnabled else { return false } + navItem = .feed + return true + case .weather: + navItem = .weather + return true + case .unknown: + return false + } + } +} diff --git a/apps/swift/Tests/PackRatTests/DeepLinkTests.swift b/apps/swift/Tests/PackRatTests/DeepLinkTests.swift index ba935da6c9..44818714fa 100644 --- a/apps/swift/Tests/PackRatTests/DeepLinkTests.swift +++ b/apps/swift/Tests/PackRatTests/DeepLinkTests.swift @@ -51,4 +51,25 @@ struct DeepLinkTests { let url = URL(string: "com.andrewbierman.packrat://oauth-callback")! #expect(DeepLink.parse(url) == .unknown(url)) } + + @MainActor + @Test("applies parsed links to native navigation state") + func appliesLinksToNavigationState() { + let state = AppState() + + #expect(state.apply(.pack(id: "pack-1"))) + #expect(state.navItem == .packs) + #expect(state.selectedPackId == "pack-1") + + #expect(state.apply(.trip(id: "trip-1"))) + #expect(state.navItem == .trips) + #expect(state.selectedTripId == "trip-1") + + #expect(state.apply(.weather)) + #expect(state.navItem == .weather) + + let unknown = URL(string: "packrat://unknown")! + #expect(!state.apply(.unknown(unknown))) + #expect(state.navItem == .weather) + } } From fd1e14bf2dfb01a09f0db5842a002c391f9d6da9 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:39:38 -0600 Subject: [PATCH 57/81] =?UTF-8?q?=F0=9F=9A=A9=20gate=20Swift=20OAuth=20but?= =?UTF-8?q?tons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PackRat/Features/Auth/LoginView.swift | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift b/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift index 48fe2f0cfe..a32257171a 100644 --- a/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift +++ b/apps/swift/Sources/PackRat/Features/Auth/LoginView.swift @@ -78,49 +78,51 @@ struct LoginView: View { .foregroundStyle(.tint) .font(.callout) - VStack(spacing: 10) { - HStack(spacing: 12) { - Divider() - Text("Or continue with") - .font(.footnote) - .foregroundStyle(.secondary) - .lineLimit(1) - Divider() - } + if AppFeatureFlags.enableOAuth { + VStack(spacing: 10) { + HStack(spacing: 12) { + Divider() + Text("Or continue with") + .font(.footnote) + .foregroundStyle(.secondary) + .lineLimit(1) + Divider() + } - #if os(iOS) - Button { - signInWithGoogle() - } label: { - Label("Continue with Google", systemImage: "g.circle") - .frame(maxWidth: .infinity) - } - .buttonStyle(.bordered) - .controlSize(.large) - .disabled(isLoading) - .accessibilityIdentifier("auth_google") + #if os(iOS) + Button { + signInWithGoogle() + } label: { + Label("Continue with Google", systemImage: "g.circle") + .frame(maxWidth: .infinity) + } + .buttonStyle(.bordered) + .controlSize(.large) + .disabled(isLoading) + .accessibilityIdentifier("auth_google") - SignInWithAppleButton(.continue) { request in - request.requestedScopes = [.fullName, .email] - } onCompletion: { result in - signInWithApple(result) - } - .signInWithAppleButtonStyle(colorScheme == .dark ? .white : .black) - .frame(height: 44) - .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) - .disabled(isLoading) - .accessibilityIdentifier("auth_apple") - #else - Button { - error = "Google sign-in is available in the iOS app. Use email sign-in on macOS for now." - } label: { - Label("Continue with Google", systemImage: "g.circle") - .frame(maxWidth: .infinity) + SignInWithAppleButton(.continue) { request in + request.requestedScopes = [.fullName, .email] + } onCompletion: { result in + signInWithApple(result) + } + .signInWithAppleButtonStyle(colorScheme == .dark ? .white : .black) + .frame(height: 44) + .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) + .disabled(isLoading) + .accessibilityIdentifier("auth_apple") + #else + Button { + error = "Google sign-in is available in the iOS app. Use email sign-in on macOS for now." + } label: { + Label("Continue with Google", systemImage: "g.circle") + .frame(maxWidth: .infinity) + } + .buttonStyle(.bordered) + .controlSize(.large) + .accessibilityIdentifier("auth_google") + #endif } - .buttonStyle(.bordered) - .controlSize(.large) - .accessibilityIdentifier("auth_google") - #endif } } } From 344f46a04d0cc7144bf149be8c956dd365929a4d Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:40:39 -0600 Subject: [PATCH 58/81] =?UTF-8?q?=F0=9F=A9=BA=20clarify=20Swift=20deployed?= =?UTF-8?q?=20auth=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts/__tests__/verify-deployed-auth.test.ts | 4 +++- apps/swift/scripts/verify-deployed-auth.ts | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts b/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts index b0644a3411..de6a2e2d57 100644 --- a/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts +++ b/apps/swift/scripts/__tests__/verify-deployed-auth.test.ts @@ -75,7 +75,9 @@ describe('verifyDeployedAuth', () => { body: { message: 'Invalid email or password', code: 'INVALID_EMAIL_OR_PASSWORD' }, }), }), - ).rejects.toThrow('Swift deployed auth preflight failed: Invalid email or password'); + ).rejects.toThrow( + 'Swift deployed auth preflight failed: Invalid email or password. Check that E2E_TEST_EMAIL/E2E_TEST_PASSWORD match a real QA user on https://api.example.test; production is not seeded by Swift CI.', + ); }); it('falls back to status when the error body is not JSON', async () => { diff --git a/apps/swift/scripts/verify-deployed-auth.ts b/apps/swift/scripts/verify-deployed-auth.ts index f564091bff..76aa4b1431 100644 --- a/apps/swift/scripts/verify-deployed-auth.ts +++ b/apps/swift/scripts/verify-deployed-auth.ts @@ -37,6 +37,14 @@ function responseMessage(input: { body: AuthResponseBody; status: number }): str return `HTTP ${status}`; } +function authFailureMessage(input: { body: AuthResponseBody; status: number; apiBaseURL: string }) { + const message = responseMessage(input); + if (input.status === 401) { + return `${message}. Check that E2E_TEST_EMAIL/E2E_TEST_PASSWORD match a real QA user on ${input.apiBaseURL}; production is not seeded by Swift CI.`; + } + return message; +} + async function parseBody(response: Pick): Promise { try { const body = await response.json(); @@ -77,9 +85,10 @@ export async function verifyDeployedAuth(input: AuthPreflightInput): Promise Date: Sat, 18 Jul 2026 18:08:40 -0600 Subject: [PATCH 59/81] =?UTF-8?q?=F0=9F=9A=A6=20enforce=20Swift=20replacem?= =?UTF-8?q?ent=20upload=20preflight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/README.md | 7 +-- .../__tests__/testflight-config.test.ts | 17 ++++++++ .../__tests__/testflight-upload-cli.test.ts | 43 ++++++++++++++++++- apps/swift/scripts/lib/testflight-config.ts | 13 ++++-- apps/swift/scripts/upload-testflight.ts | 17 ++++++++ .../scripts/verify-testflight-replacement.ts | 1 + 6 files changed, 90 insertions(+), 8 deletions(-) diff --git a/apps/swift/README.md b/apps/swift/README.md index 8d1b473466..901010fc49 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -110,9 +110,10 @@ requiring Apple credentials or running Xcode. Use `swift:testflight:preflight` before the replacement upload when validating a seamless update. It fails unless the resolved archive is the existing Expo -listing (`com.andrewbierman.packrat`, `PackRat`), Release/production, and, when -`APP_STORE_CURRENT_BUILD_NUMBER` is supplied, has a strictly greater build -number than the latest App Store Connect build. +listing (`com.andrewbierman.packrat`, `PackRat`), Release/production, and has a +strictly greater build number than `APP_STORE_CURRENT_BUILD_NUMBER`. Real +`--replacement` uploads enforce the same check before reading Apple credentials +or archiving. The same check is available from the manual **Swift E2E Tests** GitHub workflow: enable `run_testflight_preflight`, then provide `replacement_build_number` and diff --git a/apps/swift/scripts/__tests__/testflight-config.test.ts b/apps/swift/scripts/__tests__/testflight-config.test.ts index 0588f0c72c..f42411c6b8 100644 --- a/apps/swift/scripts/__tests__/testflight-config.test.ts +++ b/apps/swift/scripts/__tests__/testflight-config.test.ts @@ -175,4 +175,21 @@ describe('parseTestFlightUploadConfig', () => { 'APP_STORE_CURRENT_BUILD_NUMBER was not provided; verify the replacement build number is greater than the latest App Store Connect build before upload.', ); }); + + it('rejects missing current App Store build when strict replacement readiness is required', () => { + const config = parseTestFlightUploadConfig({ + argv: ['--replacement', '--production'], + env: { BUILD_NUMBER: '2026071802' }, + }); + + const readiness = verifyTestFlightReplacementReadiness({ + config, + requireCurrentAppStoreBuildNumber: true, + }); + + expect(readiness.ok).toBe(false); + expect(readiness.errors).toContain( + 'APP_STORE_CURRENT_BUILD_NUMBER was not provided; verify the replacement build number is greater than the latest App Store Connect build before upload.', + ); + }); }); diff --git a/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts index 42be81b1e7..7a2bcc133e 100644 --- a/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts +++ b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts @@ -1,4 +1,4 @@ -import { execFileSync } from 'node:child_process'; +import { execFileSync, spawnSync } from 'node:child_process'; import { resolve } from 'node:path'; import { env as currentEnv } from 'node:process'; import { describe, expect, it } from 'vitest'; @@ -50,4 +50,45 @@ describe('upload-testflight CLI', () => { errors: [], }); }); + + it('fails replacement preflight CLI without the current App Store build number', () => { + const env = { ...currentEnv, BUILD_NUMBER: '2026071802' }; + delete env.APP_STORE_CURRENT_BUILD_NUMBER; + + const result = spawnSync('bun', [verifyScript, '--replacement', '--production'], { + cwd: repoRoot, + encoding: 'utf8', + env, + }); + + expect(result.status).toBe(1); + const report = JSON.parse(result.stdout); + expect(report.ok).toBe(false); + expect(report.errors).toContain( + 'APP_STORE_CURRENT_BUILD_NUMBER was not provided; verify the replacement build number is greater than the latest App Store Connect build before upload.', + ); + }); + + it('blocks real replacement uploads before Apple credentials when current build is missing', () => { + const env = { + ...currentEnv, + APPLE_ID: 'tester@example.com', + APPLE_APP_PASSWORD: 'test-app-password', + APPLE_TEAM_ID: 'TEAM123', + BUILD_NUMBER: '2026071802', + }; + delete env.APP_STORE_CURRENT_BUILD_NUMBER; + + const result = spawnSync('bun', [script, '--replacement', '--production'], { + cwd: repoRoot, + encoding: 'utf8', + env, + }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain( + 'Replacement TestFlight preflight failed: APP_STORE_CURRENT_BUILD_NUMBER was not provided', + ); + expect(result.stderr).not.toContain('xcodebuild archive'); + }); }); diff --git a/apps/swift/scripts/lib/testflight-config.ts b/apps/swift/scripts/lib/testflight-config.ts index c26002f257..4948ec4ef4 100644 --- a/apps/swift/scripts/lib/testflight-config.ts +++ b/apps/swift/scripts/lib/testflight-config.ts @@ -17,6 +17,7 @@ export type TestFlightUploadConfig = { export type TestFlightReplacementReadinessInput = { config: TestFlightUploadConfig; currentAppStoreBuildNumber?: string | undefined; + requireCurrentAppStoreBuildNumber?: boolean | undefined; }; export type TestFlightReplacementReadiness = { @@ -98,7 +99,7 @@ function isPositiveInteger(value: string): boolean { export function verifyTestFlightReplacementReadiness( input: TestFlightReplacementReadinessInput, ): TestFlightReplacementReadiness { - const { config, currentAppStoreBuildNumber } = input; + const { config, currentAppStoreBuildNumber, requireCurrentAppStoreBuildNumber = false } = input; const errors: string[] = []; const warnings: string[] = []; @@ -146,9 +147,13 @@ export function verifyTestFlightReplacementReadiness( ); } } else { - warnings.push( - 'APP_STORE_CURRENT_BUILD_NUMBER was not provided; verify the replacement build number is greater than the latest App Store Connect build before upload.', - ); + const message = + 'APP_STORE_CURRENT_BUILD_NUMBER was not provided; verify the replacement build number is greater than the latest App Store Connect build before upload.'; + if (requireCurrentAppStoreBuildNumber) { + errors.push(message); + } else { + warnings.push(message); + } } return { diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index 4632138b3f..50b7ed7bea 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -21,6 +21,9 @@ * APPLE_ASC_PROVIDER App Store Connect provider short name for altool; * defaults to APPLE_TEAM_ID when omitted * BUILD_NUMBER CFBundleVersion for this upload (default: timestamp) + * APP_STORE_CURRENT_BUILD_NUMBER + * Required for --replacement uploads; latest existing + * PackRat App Store/TestFlight build number. * * Flags: * --replacement Archive for the existing Expo/App Store iOS listing. @@ -47,6 +50,7 @@ import { parseTestFlightUploadConfig, TestFlightConfigError, type TestFlightUploadConfig, + verifyTestFlightReplacementReadiness, xcodeArchiveOverrides, } from './lib/testflight-config'; import { findExportedIPA } from './lib/testflight-export'; @@ -132,6 +136,19 @@ if (nodeEnv.BUILD_NUMBER) { uploadConfig = { ...uploadConfig, buildNumber: nodeEnv.BUILD_NUMBER }; } +if (uploadConfig.lane === 'replacement') { + const readiness = verifyTestFlightReplacementReadiness({ + config: uploadConfig, + currentAppStoreBuildNumber: nodeEnv.APP_STORE_CURRENT_BUILD_NUMBER, + requireCurrentAppStoreBuildNumber: true, + }); + if (!readiness.ok) { + for (const error of readiness.errors) + console.error(`Replacement TestFlight preflight failed: ${error}`); + process.exit(1); + } +} + const appleId = req({ name: 'APPLE_ID' }); const appPassword = req({ name: 'APPLE_APP_PASSWORD' }); const teamId = req({ name: 'APPLE_TEAM_ID' }); diff --git a/apps/swift/scripts/verify-testflight-replacement.ts b/apps/swift/scripts/verify-testflight-replacement.ts index 823cda4ed1..01d4d2e6b2 100644 --- a/apps/swift/scripts/verify-testflight-replacement.ts +++ b/apps/swift/scripts/verify-testflight-replacement.ts @@ -43,6 +43,7 @@ try { const readiness = verifyTestFlightReplacementReadiness({ config, currentAppStoreBuildNumber: nodeEnv.APP_STORE_CURRENT_BUILD_NUMBER, + requireCurrentAppStoreBuildNumber: true, }); const report = { lane: config.lane, From 2cc1e671b92f59263a3d20bf0d194a1e8d67f213 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 18:44:23 -0600 Subject: [PATCH 60/81] =?UTF-8?q?=F0=9F=9A=A6=20harden=20Swift=20TestFligh?= =?UTF-8?q?t=20verification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 72 ++++++- .github/workflows/swift-visual.yml | 10 +- apps/swift/README.md | 15 ++ .../__tests__/testflight-binary.test.ts | 85 ++++++++ apps/swift/scripts/lib/testflight-binary.ts | 185 ++++++++++++++++++ apps/swift/scripts/upload-testflight.ts | 40 +++- 6 files changed, 396 insertions(+), 11 deletions(-) create mode 100644 apps/swift/scripts/__tests__/testflight-binary.test.ts create mode 100644 apps/swift/scripts/lib/testflight-binary.ts diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index 6292853b77..98a182b504 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -68,6 +68,11 @@ on: required: false type: boolean default: false + run_testflight_archive_verification: + description: "Archive/export the replacement app on a Mac runner and verify the built binary metadata without uploading" + required: false + type: boolean + default: false replacement_build_number: description: "Swift replacement build number intended for upload" required: false @@ -127,6 +132,53 @@ jobs: fi bun swift:testflight:preflight --replacement --production + testflight-replacement-archive: + name: TestFlight replacement archive verification + runs-on: macos-15 + timeout-minutes: 60 + if: github.event_name == 'workflow_dispatch' && inputs.run_testflight_archive_verification + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: ${{ env.XCODE_VERSION }} + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + env: + PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN: ${{ secrets.PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN }} + run: bun install --frozen-lockfile + + - name: Install xcodegen + run: brew install xcodegen + + - name: Generate Swift Xcode project + run: bun run swift + + - name: Verify replacement archive/export metadata + env: + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + BUILD_NUMBER: ${{ inputs.replacement_build_number }} + APP_STORE_CURRENT_BUILD_NUMBER: ${{ inputs.current_app_store_build_number }} + run: | + missing=() + [ -z "${APPLE_TEAM_ID:-}" ] && missing+=("APPLE_TEAM_ID") + [ -z "${BUILD_NUMBER:-}" ] && missing+=("replacement_build_number") + [ -z "${APP_STORE_CURRENT_BUILD_NUMBER:-}" ] && missing+=("current_app_store_build_number") + if [ ${#missing[@]} -gt 0 ]; then + echo "::error::Required TestFlight archive verification inputs missing: ${missing[*]}" + exit 1 + fi + bun apps/swift/scripts/upload-testflight.ts --replacement --production --verify-archive-only + macos-ui: name: macOS Swift UI E2E runs-on: [self-hosted, macOS, packrat-e2e] @@ -270,6 +322,7 @@ jobs: timeout-minutes: 60 if: > github.event_name == 'schedule' || + github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.run_ios_ui) steps: @@ -295,6 +348,15 @@ jobs: - name: Resolve Swift E2E target run: | target="${{ inputs.api_environment || 'configured-secret' }}" + if [ "${{ github.event_name }}" = "pull_request" ]; then + target="local" + { + echo "E2E_EMAIL=${E2E_EMAIL:-e2e@packrattest.local}" + echo "E2E_PASSWORD=${E2E_PASSWORD:-E2eTestPass123!}" + echo "E2E_TEST_EMAIL=${E2E_EMAIL:-e2e@packrattest.local}" + echo "E2E_TEST_PASSWORD=${E2E_PASSWORD:-E2eTestPass123!}" + } >> "$GITHUB_ENV" + fi case "$target" in configured-secret) echo "PACKRAT_ENV=dev" >> "$GITHUB_ENV" @@ -317,6 +379,9 @@ jobs: - name: Verify Swift E2E secrets run: | target="${{ inputs.api_environment || 'configured-secret' }}" + if [ "${{ github.event_name }}" = "pull_request" ]; then + target="local" + fi missing=() if [ "$target" != "local" ]; then [ -z "${E2E_EMAIL:-}" ] && missing+=("E2E_TEST_EMAIL") @@ -339,7 +404,7 @@ jobs: run: bun run swift - name: Seed E2E test user - if: ${{ (inputs.api_environment || 'configured-secret') != 'local' && (inputs.api_environment || 'configured-secret') != 'production' }} + if: ${{ github.event_name != 'pull_request' && (inputs.api_environment || 'configured-secret') != 'local' && (inputs.api_environment || 'configured-secret') != 'production' }} run: bun run --filter @packrat/api db:seed:e2e-user env: NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }} @@ -347,7 +412,7 @@ jobs: E2E_TEST_PASSWORD: ${{ env.E2E_PASSWORD }} - name: Verify deployed Swift auth - if: ${{ (inputs.api_environment || 'configured-secret') != 'local' }} + if: ${{ github.event_name != 'pull_request' && (inputs.api_environment || 'configured-secret') != 'local' }} run: bun run apps/swift/scripts/verify-deployed-auth.ts - name: Boot iOS simulator @@ -361,6 +426,9 @@ jobs: - name: Run iOS Swift UI E2E run: | plan="${{ inputs.ios_test_plan || 'full' }}" + if [ "${{ github.event_name }}" = "pull_request" ]; then + plan="sanity" + fi if [ "$plan" = "full" ]; then bun run e2e:swift:ios elif [ "$plan" = "smoke" ]; then diff --git a/.github/workflows/swift-visual.yml b/.github/workflows/swift-visual.yml index 35777b05e3..c493fff998 100644 --- a/.github/workflows/swift-visual.yml +++ b/.github/workflows/swift-visual.yml @@ -21,11 +21,11 @@ on: description: "API target for screenshot capture" required: false type: choice - default: local + default: production options: + - production - local - dev - - production feature_flag_profile: description: "Temporary Swift feature flag profile" required: false @@ -96,7 +96,7 @@ jobs: - name: Resolve Swift visual target run: | - target="${{ inputs.api_environment || 'local' }}" + target="${{ inputs.api_environment || 'production' }}" case "$target" in local) echo "PACKRAT_ENV=local" >> "$GITHUB_ENV" @@ -114,7 +114,7 @@ jobs: echo "PACKRAT_SWIFT_FEATURE_FLAG_PROFILE=${{ inputs.feature_flag_profile || 'default' }}" >> "$GITHUB_ENV" - name: Verify Swift visual deployed prerequisites - if: ${{ (inputs.api_environment || 'local') == 'dev' }} + if: ${{ (inputs.api_environment || 'production') == 'dev' }} env: NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }} run: | @@ -126,7 +126,7 @@ jobs: fi - name: Seed dev E2E test user - if: ${{ (inputs.api_environment || 'local') == 'dev' }} + if: ${{ (inputs.api_environment || 'production') == 'dev' }} run: bun run --filter @packrat/api db:seed:e2e-user env: NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }} diff --git a/apps/swift/README.md b/apps/swift/README.md index 901010fc49..eb004003de 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -97,6 +97,7 @@ wrong App Store Connect record: APP_STORE_CURRENT_BUILD_NUMBER=2026071801 BUILD_NUMBER=2026071802 \ bun swift:testflight:preflight --replacement --production bun apps/swift/scripts/upload-testflight.ts --replacement --dry-run +bun apps/swift/scripts/upload-testflight.ts --replacement --verify-archive-only bun apps/swift/scripts/upload-testflight.ts --replacement bun apps/swift/scripts/upload-testflight.ts --side-by-side --staging ``` @@ -108,6 +109,12 @@ Use `--dry-run` before a real upload to verify the lane, bundle id, display name, build configuration, API environment, and Xcode archive overrides without requiring Apple credentials or running Xcode. +Use `--verify-archive-only` on a Mac signing runner before a real replacement +upload when you want TestFlight-level confidence without shipping a build. It +archives, exports, and inspects the built app/IPA metadata for the replacement +bundle ids, display name, build number, production `PACKRAT_ENV`, and embedded +watch companion linkage, then exits before upload. + Use `swift:testflight:preflight` before the replacement upload when validating a seamless update. It fails unless the resolved archive is the existing Expo listing (`com.andrewbierman.packrat`, `PackRat`), Release/production, and has a @@ -120,6 +127,14 @@ enable `run_testflight_preflight`, then provide `replacement_build_number` and `current_app_store_build_number`. That verifies the replacement metadata without archiving or uploading. +The manual **Swift E2E Tests** workflow can also run +`run_testflight_archive_verification` with the same build-number inputs. That +uses a macOS runner to archive/export and verify the actual binary metadata +without uploading to TestFlight. The **Swift Visual Screenshots** workflow +defaults to production real-auth screenshots so the recurring contact sheets do +not accidentally prove only local seeded behavior; choose `local` explicitly for +fixture-backed visual review. + ## Data Isolation Swift E2E tests use unique names for records they create. That keeps repeated diff --git a/apps/swift/scripts/__tests__/testflight-binary.test.ts b/apps/swift/scripts/__tests__/testflight-binary.test.ts new file mode 100644 index 0000000000..665d8d0739 --- /dev/null +++ b/apps/swift/scripts/__tests__/testflight-binary.test.ts @@ -0,0 +1,85 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { verifyTestFlightArchive } from '../lib/testflight-binary'; +import { parseTestFlightUploadConfig } from '../lib/testflight-config'; + +const replacementConfig = parseTestFlightUploadConfig({ + argv: ['--replacement', '--production'], + env: { BUILD_NUMBER: '2026071802' }, +}); + +function plist(values: Record): string { + const body = Object.entries(values) + .map(([key, value]) => `\t${key}\n\t${value}`) + .join('\n'); + return ` + + + +${body} + + +`; +} + +function writeArchive(input?: { iosBundleId?: string; packratEnv?: string }): string { + const root = mkdtempSync(join(tmpdir(), 'packrat-archive-test-')); + const iosApp = join(root, 'PackRat.xcarchive', 'Products', 'Applications', 'PackRat.app'); + const watchApp = join(iosApp, 'Watch', 'PackRat Watch.app'); + mkdirSync(watchApp, { recursive: true }); + writeFileSync( + join(iosApp, 'Info.plist'), + plist({ + CFBundleIdentifier: input?.iosBundleId ?? 'com.andrewbierman.packrat', + CFBundleDisplayName: 'PackRat', + CFBundleVersion: '2026071802', + PACKRAT_ENV: input?.packratEnv ?? 'production', + }), + ); + writeFileSync( + join(watchApp, 'Info.plist'), + plist({ + CFBundleIdentifier: 'com.andrewbierman.packrat.watchkitapp', + CFBundleDisplayName: 'PackRat', + CFBundleVersion: '2026071802', + WKCompanionAppBundleIdentifier: 'com.andrewbierman.packrat', + }), + ); + return join(root, 'PackRat.xcarchive'); +} + +describe('TestFlight binary verification', () => { + it('accepts a replacement archive with production metadata', () => { + const archivePath = writeArchive(); + try { + const result = verifyTestFlightArchive({ archivePath, config: replacementConfig }); + expect(result).toMatchObject({ + ok: true, + errors: [], + }); + expect(result.iosApp).toContain('PackRat.app'); + expect(result.watchApp).toContain('PackRat Watch.app'); + } finally { + rmSync(join(archivePath, '..'), { recursive: true, force: true }); + } + }); + + it('rejects an archive that still has side-by-side or non-production metadata', () => { + const archivePath = writeArchive({ + iosBundleId: 'com.andrewbierman.packrat.swift', + packratEnv: 'dev', + }); + try { + const result = verifyTestFlightArchive({ archivePath, config: replacementConfig }); + expect(result.ok).toBe(false); + expect(result.errors).toContain( + 'iOS bundle id: expected com.andrewbierman.packrat, got com.andrewbierman.packrat.swift.', + ); + expect(result.errors).toContain('iOS API environment: expected production, got dev.'); + } finally { + rmSync(join(archivePath, '..'), { recursive: true, force: true }); + } + }); +}); diff --git a/apps/swift/scripts/lib/testflight-binary.ts b/apps/swift/scripts/lib/testflight-binary.ts new file mode 100644 index 0000000000..733c13377b --- /dev/null +++ b/apps/swift/scripts/lib/testflight-binary.ts @@ -0,0 +1,185 @@ +import { execFileSync } from 'node:child_process'; +import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { basename, join } from 'node:path'; +import type { TestFlightUploadConfig } from './testflight-config'; + +type PlistValue = string | boolean | number | null; +type Plist = Record; + +export type TestFlightBinaryVerification = { + ok: boolean; + errors: string[]; + iosApp: string | null; + watchApp: string | null; +}; + +function xmlUnescape(value: string): string { + return value + .replaceAll('"', '"') + .replaceAll(''', "'") + .replaceAll('>', '>') + .replaceAll('<', '<') + .replaceAll('&', '&'); +} + +function parseXmlPlistStrings(xml: string): Plist { + const result: Plist = {}; + const pattern = + /([^<]+)<\/key>\s*(?:([\s\S]*?)<\/string>|([\s\S]*?)<\/integer>|<(true|false)\/>)/g; + for (const match of xml.matchAll(pattern)) { + const key = xmlUnescape(match[1]); + if (match[2] !== undefined) result[key] = xmlUnescape(match[2]); + else if (match[3] !== undefined) result[key] = Number(match[3]); + else result[key] = match[4] === 'true'; + } + return result; +} + +function readPlist(path: string): Plist { + try { + return JSON.parse( + execFileSync('plutil', ['-convert', 'json', '-o', '-', path], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }), + ); + } catch { + return parseXmlPlistStrings(readFileSync(path, 'utf8')); + } +} + +function findAppBundles(root: string): string[] { + if (!existsSync(root)) return []; + const entries = readdirSync(root).map((entry) => join(root, entry)); + const apps: string[] = []; + for (const entry of entries) { + const stats = statSync(entry); + if (!stats.isDirectory()) continue; + if (entry.endsWith('.app') && existsSync(join(entry, 'Info.plist'))) { + apps.push(entry); + continue; + } + apps.push(...findAppBundles(entry)); + } + return apps; +} + +function plistString(plist: Plist, key: string): string { + const value = plist[key]; + return typeof value === 'string' ? value : String(value ?? ''); +} + +function expectEqual(input: { errors: string[]; label: string; actual: string; expected: string }) { + const { errors, label, actual, expected } = input; + if (actual !== expected) + errors.push(`${label}: expected ${expected}, got ${actual || ''}.`); +} + +function verifyAppBundles(input: { + root: string; + config: TestFlightUploadConfig; +}): TestFlightBinaryVerification { + const { root, config } = input; + const errors: string[] = []; + const apps = findAppBundles(root); + const iosApp = + apps.find( + (app) => !app.includes('/Watch/') && basename(app).toLowerCase().includes('packrat'), + ) ?? + apps.find((app) => !app.includes('/Watch/')) ?? + null; + + if (!iosApp) { + return { + ok: false, + errors: ['Could not find iOS .app bundle in archive/export.'], + iosApp, + watchApp: null, + }; + } + + const iosPlist = readPlist(join(iosApp, 'Info.plist')); + expectEqual({ + errors, + label: 'iOS bundle id', + actual: plistString(iosPlist, 'CFBundleIdentifier'), + expected: config.bundleId, + }); + expectEqual({ + errors, + label: 'iOS display name', + actual: plistString(iosPlist, 'CFBundleDisplayName'), + expected: config.displayName, + }); + expectEqual({ + errors, + label: 'iOS build number', + actual: plistString(iosPlist, 'CFBundleVersion'), + expected: config.buildNumber, + }); + expectEqual({ + errors, + label: 'iOS API environment', + actual: plistString(iosPlist, 'PACKRAT_ENV'), + expected: config.apiEnvironment, + }); + + const watchApp = findAppBundles(join(iosApp, 'Watch'))[0] ?? null; + if (!watchApp) { + errors.push('Could not find embedded watchOS .app bundle.'); + } else { + const watchPlist = readPlist(join(watchApp, 'Info.plist')); + expectEqual({ + errors, + label: 'watchOS bundle id', + actual: plistString(watchPlist, 'CFBundleIdentifier'), + expected: config.watchBundleId, + }); + expectEqual({ + errors, + label: 'watchOS companion bundle id', + actual: plistString(watchPlist, 'WKCompanionAppBundleIdentifier'), + expected: config.companionBundleId, + }); + expectEqual({ + errors, + label: 'watchOS display name', + actual: plistString(watchPlist, 'CFBundleDisplayName'), + expected: config.displayName, + }); + expectEqual({ + errors, + label: 'watchOS build number', + actual: plistString(watchPlist, 'CFBundleVersion'), + expected: config.buildNumber, + }); + } + + return { ok: errors.length === 0, errors, iosApp, watchApp }; +} + +export function verifyTestFlightArchive(input: { + archivePath: string; + config: TestFlightUploadConfig; +}): TestFlightBinaryVerification { + return verifyAppBundles({ + root: join(input.archivePath, 'Products', 'Applications'), + config: input.config, + }); +} + +export function verifyTestFlightIPA(input: { + ipaPath: string; + config: TestFlightUploadConfig; +}): TestFlightBinaryVerification { + const work = mkdtempSync(join(tmpdir(), 'packrat-ipa-verify-')); + try { + execFileSync('unzip', ['-q', input.ipaPath, '-d', work], { + stdio: ['ignore', 'ignore', 'pipe'], + }); + return verifyAppBundles({ root: join(work, 'Payload'), config: input.config }); + } finally { + rmSync(work, { recursive: true, force: true }); + } +} diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index 50b7ed7bea..0e59ce7c1b 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -34,10 +34,13 @@ * API profile when --staging is absent. * --dry-run Print the resolved archive identity/settings and exit * before reading Apple credentials or running Xcode. + * --verify-archive-only Archive, export, inspect binary metadata, then exit + * before reading Apple ID upload credentials. * * Usage: * bun apps/swift/scripts/upload-testflight.ts --replacement * bun apps/swift/scripts/upload-testflight.ts --replacement --dry-run + * bun apps/swift/scripts/upload-testflight.ts --replacement --verify-archive-only * bun apps/swift/scripts/upload-testflight.ts --side-by-side --staging */ import { execFileSync } from 'node:child_process'; @@ -46,6 +49,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { nodeEnv } from '@packrat/env/node'; import { safeJsonStringify } from '@packrat/utils'; +import { verifyTestFlightArchive, verifyTestFlightIPA } from './lib/testflight-binary'; import { parseTestFlightUploadConfig, TestFlightConfigError, @@ -58,11 +62,13 @@ import { findExportedIPA } from './lib/testflight-export'; const SWIFT_DIR = new URL('..', import.meta.url).pathname; const PROJECT = join(SWIFT_DIR, 'PackRat.xcodeproj'); const HELP = process.argv.includes('--help') || process.argv.includes('-h'); +const VERIFY_ARCHIVE_ONLY = process.argv.includes('--verify-archive-only'); function usage(): string { return [ 'Usage:', ' bun apps/swift/scripts/upload-testflight.ts --replacement [--production|--staging] [--dry-run]', + ' bun apps/swift/scripts/upload-testflight.ts --replacement [--production|--staging] --verify-archive-only', ' bun apps/swift/scripts/upload-testflight.ts --side-by-side [--production|--staging] [--dry-run]', '', 'Lanes:', @@ -149,9 +155,9 @@ if (uploadConfig.lane === 'replacement') { } } -const appleId = req({ name: 'APPLE_ID' }); -const appPassword = req({ name: 'APPLE_APP_PASSWORD' }); const teamId = req({ name: 'APPLE_TEAM_ID' }); +const appleId = VERIFY_ARCHIVE_ONLY ? undefined : req({ name: 'APPLE_ID' }); +const appPassword = VERIFY_ARCHIVE_ONLY ? undefined : req({ name: 'APPLE_APP_PASSWORD' }); const ascProvider = nodeEnv.APPLE_ASC_PROVIDER ?? teamId; printPreflight({ config: uploadConfig, teamId, ascProvider }); @@ -165,6 +171,18 @@ function run(input: { cmd: string; args: string[] }) { execFileSync(cmd, args, { stdio: 'inherit' }); } +function verifyBinary(input: { + label: string; + result: ReturnType; +}) { + const { label, result } = input; + if (!result.ok) { + for (const error of result.errors) console.error(`${label} verification failed: ${error}`); + process.exit(1); + } + console.log(`✓ Verified ${label} metadata (${result.iosApp}, ${result.watchApp})`); +} + // 1. Archive for a real device (TestFlight cannot accept a simulator build). run({ cmd: 'xcodebuild', @@ -186,6 +204,10 @@ run({ ...xcodeArchiveOverrides({ config: uploadConfig, teamId }), ], }); +verifyBinary({ + label: 'TestFlight archive', + result: verifyTestFlightArchive({ archivePath, config: uploadConfig }), +}); // 2. Export a signed .ipa for App Store distribution. const exportOptions = join(work, 'ExportOptions.plist'); @@ -225,6 +247,16 @@ run({ // `--asc-provider` (team short name) is required when the Apple ID belongs to // more than one team, so altool knows which one to deliver to. const ipa = findExportedIPA(exportDir); +verifyBinary({ + label: 'TestFlight IPA', + result: verifyTestFlightIPA({ ipaPath: ipa, config: uploadConfig }), +}); + +if (VERIFY_ARCHIVE_ONLY) { + console.log('\n✓ Archive/export verification passed; skipping TestFlight upload.'); + process.exit(0); +} + run({ cmd: 'xcrun', args: [ @@ -235,9 +267,9 @@ run({ '--file', ipa, '--username', - appleId, + appleId ?? '', '--password', - appPassword, + appPassword ?? '', '--asc-provider', ascProvider, ], From 93240faf27646aa49615fe6bd86a945a146564a3 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 18:46:05 -0600 Subject: [PATCH 61/81] =?UTF-8?q?=F0=9F=94=A7=20normalize=20blank=20E2E=20?= =?UTF-8?q?API=20env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/env/src/node.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/env/src/node.ts b/packages/env/src/node.ts index 3b43d52ced..5487135f5c 100644 --- a/packages/env/src/node.ts +++ b/packages/env/src/node.ts @@ -128,6 +128,10 @@ export const nodeEnvSchema = z.object({ export type NodeEnv = z.infer; +function optionalEnv(value: string | undefined): string | undefined { + return value?.trim() ? value : undefined; +} + /** * Typed env parsed from `process.env` at module load. Throws a Zod * validation error if any value fails its schema constraint. @@ -167,7 +171,7 @@ export const nodeEnv = nodeEnvSchema.parse({ E2E_PASSWORD: process.env.E2E_PASSWORD, E2E_TEST_EMAIL: process.env.E2E_TEST_EMAIL, E2E_TEST_PASSWORD: process.env.E2E_TEST_PASSWORD, - E2E_API_BASE_URL: process.env.E2E_API_BASE_URL, + E2E_API_BASE_URL: optionalEnv(process.env.E2E_API_BASE_URL), OPENAI_API_KEY: process.env.OPENAI_API_KEY, WEATHER_API_KEY: process.env.WEATHER_API_KEY, APPLE_PRIVATE_KEY: process.env.APPLE_PRIVATE_KEY, From 1bc08edf1acaa0d6c4d9adb00d801af05380546f Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 18:51:52 -0600 Subject: [PATCH 62/81] =?UTF-8?q?=F0=9F=A7=AA=20run=20Swift=20PR=20sanity?= =?UTF-8?q?=20on=20deployed=20dev?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index 98a182b504..cdd957c2fe 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -349,13 +349,7 @@ jobs: run: | target="${{ inputs.api_environment || 'configured-secret' }}" if [ "${{ github.event_name }}" = "pull_request" ]; then - target="local" - { - echo "E2E_EMAIL=${E2E_EMAIL:-e2e@packrattest.local}" - echo "E2E_PASSWORD=${E2E_PASSWORD:-E2eTestPass123!}" - echo "E2E_TEST_EMAIL=${E2E_EMAIL:-e2e@packrattest.local}" - echo "E2E_TEST_PASSWORD=${E2E_PASSWORD:-E2eTestPass123!}" - } >> "$GITHUB_ENV" + target="dev" fi case "$target" in configured-secret) @@ -380,7 +374,7 @@ jobs: run: | target="${{ inputs.api_environment || 'configured-secret' }}" if [ "${{ github.event_name }}" = "pull_request" ]; then - target="local" + target="dev" fi missing=() if [ "$target" != "local" ]; then @@ -404,7 +398,7 @@ jobs: run: bun run swift - name: Seed E2E test user - if: ${{ github.event_name != 'pull_request' && (inputs.api_environment || 'configured-secret') != 'local' && (inputs.api_environment || 'configured-secret') != 'production' }} + if: ${{ (github.event_name == 'pull_request' || (inputs.api_environment || 'configured-secret') != 'local') && (inputs.api_environment || 'configured-secret') != 'production' }} run: bun run --filter @packrat/api db:seed:e2e-user env: NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }} @@ -412,7 +406,7 @@ jobs: E2E_TEST_PASSWORD: ${{ env.E2E_PASSWORD }} - name: Verify deployed Swift auth - if: ${{ github.event_name != 'pull_request' && (inputs.api_environment || 'configured-secret') != 'local' }} + if: ${{ github.event_name == 'pull_request' || (inputs.api_environment || 'configured-secret') != 'local' }} run: bun run apps/swift/scripts/verify-deployed-auth.ts - name: Boot iOS simulator From e5a1f8093c7ef0d2bccf69805245fa4bf5be4c39 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 19:04:06 -0600 Subject: [PATCH 63/81] =?UTF-8?q?=F0=9F=A7=B9=20satisfy=20TestFlight=20ver?= =?UTF-8?q?ifier=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/scripts/lib/testflight-binary.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/swift/scripts/lib/testflight-binary.ts b/apps/swift/scripts/lib/testflight-binary.ts index 733c13377b..347bd2228a 100644 --- a/apps/swift/scripts/lib/testflight-binary.ts +++ b/apps/swift/scripts/lib/testflight-binary.ts @@ -65,7 +65,8 @@ function findAppBundles(root: string): string[] { return apps; } -function plistString(plist: Plist, key: string): string { +function plistString(input: { plist: Plist; key: string }): string { + const { plist, key } = input; const value = plist[key]; return typeof value === 'string' ? value : String(value ?? ''); } @@ -103,29 +104,29 @@ function verifyAppBundles(input: { expectEqual({ errors, label: 'iOS bundle id', - actual: plistString(iosPlist, 'CFBundleIdentifier'), + actual: plistString({ plist: iosPlist, key: 'CFBundleIdentifier' }), expected: config.bundleId, }); expectEqual({ errors, label: 'iOS display name', - actual: plistString(iosPlist, 'CFBundleDisplayName'), + actual: plistString({ plist: iosPlist, key: 'CFBundleDisplayName' }), expected: config.displayName, }); expectEqual({ errors, label: 'iOS build number', - actual: plistString(iosPlist, 'CFBundleVersion'), + actual: plistString({ plist: iosPlist, key: 'CFBundleVersion' }), expected: config.buildNumber, }); expectEqual({ errors, label: 'iOS API environment', - actual: plistString(iosPlist, 'PACKRAT_ENV'), + actual: plistString({ plist: iosPlist, key: 'PACKRAT_ENV' }), expected: config.apiEnvironment, }); - const watchApp = findAppBundles(join(iosApp, 'Watch'))[0] ?? null; + const watchApp = findAppBundles(join(iosApp, 'Watch')).at(0) ?? null; if (!watchApp) { errors.push('Could not find embedded watchOS .app bundle.'); } else { @@ -133,25 +134,25 @@ function verifyAppBundles(input: { expectEqual({ errors, label: 'watchOS bundle id', - actual: plistString(watchPlist, 'CFBundleIdentifier'), + actual: plistString({ plist: watchPlist, key: 'CFBundleIdentifier' }), expected: config.watchBundleId, }); expectEqual({ errors, label: 'watchOS companion bundle id', - actual: plistString(watchPlist, 'WKCompanionAppBundleIdentifier'), + actual: plistString({ plist: watchPlist, key: 'WKCompanionAppBundleIdentifier' }), expected: config.companionBundleId, }); expectEqual({ errors, label: 'watchOS display name', - actual: plistString(watchPlist, 'CFBundleDisplayName'), + actual: plistString({ plist: watchPlist, key: 'CFBundleDisplayName' }), expected: config.displayName, }); expectEqual({ errors, label: 'watchOS build number', - actual: plistString(watchPlist, 'CFBundleVersion'), + actual: plistString({ plist: watchPlist, key: 'CFBundleVersion' }), expected: config.buildNumber, }); } From f1ef84b1f18d3d0b394a041020dbc9a0878c0cf6 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 19:24:36 -0600 Subject: [PATCH 64/81] =?UTF-8?q?=F0=9F=94=A7=20use=20safe=20guards=20in?= =?UTF-8?q?=20TestFlight=20verifier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/scripts/lib/testflight-binary.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/swift/scripts/lib/testflight-binary.ts b/apps/swift/scripts/lib/testflight-binary.ts index 347bd2228a..5acaf4a211 100644 --- a/apps/swift/scripts/lib/testflight-binary.ts +++ b/apps/swift/scripts/lib/testflight-binary.ts @@ -2,6 +2,8 @@ import { execFileSync } from 'node:child_process'; import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { basename, join } from 'node:path'; +import { isString } from '@packrat/guards'; +import { safeJsonParse } from '@packrat/utils'; import type { TestFlightUploadConfig } from './testflight-config'; type PlistValue = string | boolean | number | null; @@ -38,11 +40,12 @@ function parseXmlPlistStrings(xml: string): Plist { function readPlist(path: string): Plist { try { - return JSON.parse( + return safeJsonParse( execFileSync('plutil', ['-convert', 'json', '-o', '-', path], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], }), + { strict: true }, ); } catch { return parseXmlPlistStrings(readFileSync(path, 'utf8')); @@ -68,7 +71,7 @@ function findAppBundles(root: string): string[] { function plistString(input: { plist: Plist; key: string }): string { const { plist, key } = input; const value = plist[key]; - return typeof value === 'string' ? value : String(value ?? ''); + return isString(value) ? value : String(value ?? ''); } function expectEqual(input: { errors: string[]; label: string; actual: string; expected: string }) { From 55732171b4b336656b1aed25d62a1c0ba0eedd23 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 19:41:46 -0600 Subject: [PATCH 65/81] =?UTF-8?q?=F0=9F=A7=AA=20harden=20iOS=20login=20sub?= =?UTF-8?q?mit=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Tests/PackRatUITests/AppUITestCase.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift index 49a51590b0..05a09708d8 100644 --- a/apps/swift/Tests/PackRatUITests/AppUITestCase.swift +++ b/apps/swift/Tests/PackRatUITests/AppUITestCase.swift @@ -185,7 +185,12 @@ class AppUITestCase: XCTestCase { } else if returnButton.waitForExistence(timeout: 1) { returnButton.tap() } else { - submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + app.swipeUp() + if submitButton.waitForExistence(timeout: 2), submitButton.isHittable { + submitButton.tap() + } else { + app.typeText("\n") + } } } #endif From 39f9e91de0f0ddbf7a7744dff4a73a672bc8781d Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 18 Jul 2026 22:29:47 -0600 Subject: [PATCH 66/81] =?UTF-8?q?=F0=9F=A7=AA=20preserve=20E2E=20bootstrap?= =?UTF-8?q?=20failure=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api/scripts/e2e-local-start.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/scripts/e2e-local-start.ts b/packages/api/scripts/e2e-local-start.ts index 10f8fbb186..3e9646a4a9 100644 --- a/packages/api/scripts/e2e-local-start.ts +++ b/packages/api/scripts/e2e-local-start.ts @@ -35,11 +35,11 @@ async function run(opts: { ? await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text()]) : ['', '']; const exitCode = await child.exited; - if (exitCode !== 0) throw new Error(`${command.join(' ')} exited with ${exitCode}`); if (opts.captureOutput) { if (stdout) process.stdout.write(stdout); if (stderr) process.stderr.write(stderr); } + if (exitCode !== 0) throw new Error(`${command.join(' ')} exited with ${exitCode}`); return `${stdout}${stderr}`; } From 66600bcdbe3eae6c8ec50f916e471231ea6a95fd Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:31:11 -0600 Subject: [PATCH 67/81] =?UTF-8?q?=F0=9F=A7=AA=20harden=20TestFlight=20repl?= =?UTF-8?q?acement=20verification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swift-e2e.yml | 2 +- apps/swift/README.md | 13 ++++++++----- .../Tests/PackRatUITests/NavigationTests.swift | 4 ++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index cdd957c2fe..f614dc82b8 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -134,7 +134,7 @@ jobs: testflight-replacement-archive: name: TestFlight replacement archive verification - runs-on: macos-15 + runs-on: [self-hosted, macOS, packrat-e2e] timeout-minutes: 60 if: github.event_name == 'workflow_dispatch' && inputs.run_testflight_archive_verification diff --git a/apps/swift/README.md b/apps/swift/README.md index eb004003de..6fb8dad7b7 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -109,11 +109,14 @@ Use `--dry-run` before a real upload to verify the lane, bundle id, display name, build configuration, API environment, and Xcode archive overrides without requiring Apple credentials or running Xcode. -Use `--verify-archive-only` on a Mac signing runner before a real replacement -upload when you want TestFlight-level confidence without shipping a build. It -archives, exports, and inspects the built app/IPA metadata for the replacement -bundle ids, display name, build number, production `PACKRAT_ENV`, and embedded -watch companion linkage, then exits before upload. +Use `--verify-archive-only` on a self-hosted Mac signing runner before a real +replacement upload when you want TestFlight-level confidence without shipping a +build. It archives, exports, and inspects the built app/IPA metadata for the +replacement bundle ids, display name, build number, production `PACKRAT_ENV`, +and embedded watch companion linkage, then exits before upload. The hosted +GitHub macOS runners do not have a signed-in Apple account or provisioning +profiles, so the manual archive verification job runs on the same +`self-hosted`, `macOS`, `packrat-e2e` runner used for Mac app UI automation. Use `swift:testflight:preflight` before the replacement upload when validating a seamless update. It fails unless the resolved archive is the existing Expo diff --git a/apps/swift/Tests/PackRatUITests/NavigationTests.swift b/apps/swift/Tests/PackRatUITests/NavigationTests.swift index f5f3e99400..becb7a10b7 100644 --- a/apps/swift/Tests/PackRatUITests/NavigationTests.swift +++ b/apps/swift/Tests/PackRatUITests/NavigationTests.swift @@ -5,6 +5,10 @@ import XCTest /// macOS uses a different navigation idiom (NavigationSplitView sidebar); the /// equivalent suite lives in `NavigationMacOSTests` and is gated separately. final class NavigationTests: AppUITestCase { + override var executionTimeAllowance: TimeInterval { + get { 3 * 60 } + set { _ = newValue } + } // Each entry: (tab bar label, expected navigation title or landmark text) private let tabs: [(tab: String, landmark: String)] = [ From 042d554ba79654ad00ee5821e24427cdf0e7845c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:50:33 -0600 Subject: [PATCH 68/81] =?UTF-8?q?=F0=9F=A7=AA=20extend=20iOS=20sanity=20te?= =?UTF-8?q?st=20allowance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/TestPlans/iOS-Sanity.xctestplan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/swift/TestPlans/iOS-Sanity.xctestplan b/apps/swift/TestPlans/iOS-Sanity.xctestplan index 497c665349..6c787162a9 100644 --- a/apps/swift/TestPlans/iOS-Sanity.xctestplan +++ b/apps/swift/TestPlans/iOS-Sanity.xctestplan @@ -15,7 +15,7 @@ "language" : "en", "region" : "US", "testTimeoutsEnabled" : true, - "maximumTestExecutionTimeAllowance" : 60 + "maximumTestExecutionTimeAllowance" : 180 }, "testTargets" : [ { From 6609064926a021200070a09487a033d6642a1f82 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Tue, 21 Jul 2026 07:14:40 -0600 Subject: [PATCH 69/81] =?UTF-8?q?=F0=9F=90=9B=20fix=20Swift=20beta=20versi?= =?UTF-8?q?oning=20and=20weather=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Weather/WeatherViewModel.swift | 24 +++--- .../PackRat/Services/WeatherService.swift | 13 ++- .../Tests/PackRatTests/ViewModelTests.swift | 79 +++++++++++++++++++ apps/swift/project.yml | 12 +-- .../__tests__/testflight-config.test.ts | 16 ++++ .../__tests__/testflight-upload-cli.test.ts | 3 + apps/swift/scripts/lib/testflight-config.ts | 7 +- apps/swift/scripts/upload-testflight.ts | 5 +- .../scripts/verify-testflight-replacement.ts | 3 +- 9 files changed, 142 insertions(+), 20 deletions(-) diff --git a/apps/swift/Sources/PackRat/Features/Weather/WeatherViewModel.swift b/apps/swift/Sources/PackRat/Features/Weather/WeatherViewModel.swift index c03c4b229c..1c501fcf16 100644 --- a/apps/swift/Sources/PackRat/Features/Weather/WeatherViewModel.swift +++ b/apps/swift/Sources/PackRat/Features/Weather/WeatherViewModel.swift @@ -16,10 +16,10 @@ final class WeatherViewModel { var searchError: String? var forecastError: String? - private let service: WeatherService + private let service: any WeatherServicing private var searchTask: Task? - init(service: WeatherService = .shared) { + init(service: any WeatherServicing = WeatherService.shared) { self.service = service if VisualSampleData.isUITestFixturesEnabled { UserDefaults.standard.removeObject(forKey: savedLocationsKey) @@ -106,15 +106,15 @@ final class WeatherViewModel { searchResults = [] searchText = "" UserDefaults.standard.set(location.id, forKey: activeLocationKey) - await loadForecast(for: location.id) + await loadForecast(for: location) } - func loadForecast(for locationId: Int) async { + func loadForecast(for location: WeatherLocation) async { if (VisualSampleData.isEnabled || VisualSampleData.isUITestFixturesEnabled), - let location = selectedLocation ?? VisualSampleData.weatherLocations.first(where: { $0.id == locationId }) { + let selected = selectedLocation ?? VisualSampleData.weatherLocations.first(where: { $0.id == location.id }) { isLoadingForecast = false forecastError = nil - forecast = VisualSampleData.weatherForecast(for: location) + forecast = VisualSampleData.weatherForecast(for: selected) return } @@ -128,14 +128,18 @@ final class WeatherViewModel { forecastError = nil defer { isLoadingForecast = false } do { - forecast = try await service.getForecast(locationId: locationId) + forecast = try await service.getForecast(locationId: location.id) } catch { - forecastError = error.localizedDescription + do { + forecast = try await service.getForecast(query: location.displayName) + } catch { + forecastError = error.localizedDescription + } } } func refresh() async { - guard let id = selectedLocation?.id else { return } - await loadForecast(for: id) + guard let location = selectedLocation else { return } + await loadForecast(for: location) } } diff --git a/apps/swift/Sources/PackRat/Services/WeatherService.swift b/apps/swift/Sources/PackRat/Services/WeatherService.swift index fc697cf04e..59facf52e2 100644 --- a/apps/swift/Sources/PackRat/Services/WeatherService.swift +++ b/apps/swift/Sources/PackRat/Services/WeatherService.swift @@ -1,6 +1,12 @@ import Foundation -final class WeatherService: Sendable { +protocol WeatherServicing: Sendable { + func searchLocations(query: String) async throws -> [WeatherLocation] + func getForecast(locationId: Int) async throws -> WeatherForecastResponse + func getForecast(query: String) async throws -> WeatherForecastResponse +} + +final class WeatherService: WeatherServicing { static let shared = WeatherService() private let api: APIClient @@ -15,4 +21,9 @@ final class WeatherService: Sendable { let endpoint = Endpoint(.get, "/api/weather/forecast", query: ["id": "\(locationId)"]) return try await api.send(endpoint) } + + func getForecast(query: String) async throws -> WeatherForecastResponse { + let endpoint = Endpoint(.get, "/api/weather/by-name", query: ["q": query]) + return try await api.send(endpoint) + } } diff --git a/apps/swift/Tests/PackRatTests/ViewModelTests.swift b/apps/swift/Tests/PackRatTests/ViewModelTests.swift index f0b2c7c110..2b87351bd3 100644 --- a/apps/swift/Tests/PackRatTests/ViewModelTests.swift +++ b/apps/swift/Tests/PackRatTests/ViewModelTests.swift @@ -138,6 +138,85 @@ struct WeatherViewModelTests { vm.onSearchTextChanged() #expect(vm.searchResults.isEmpty) } + + @Test("selectLocation falls back to by-name forecast when id lookup fails") + @MainActor func forecastFallsBackToByName() async { + let service = FallbackWeatherService() + let vm = WeatherViewModel(service: service) + let denver = WeatherLocation( + id: 5419384, + name: "Denver", + region: "Colorado", + country: "United States", + lat: 39.74, + lon: -104.98 + ) + + await vm.selectLocation(denver) + + #expect(vm.forecast?.location?.name == "Denver") + #expect(vm.forecastError == nil) + #expect(await service.idForecastRequests == 1) + #expect(await service.nameForecastQueries == ["Denver, Colorado"]) + } +} + +private actor FallbackWeatherService: WeatherServicing { + private(set) var idForecastRequests = 0 + private(set) var nameForecastQueries: [String] = [] + + func searchLocations(query: String) async throws -> [WeatherLocation] { + [ + WeatherLocation( + id: 5419384, + name: "Denver", + region: "Colorado", + country: "United States", + lat: 39.74, + lon: -104.98 + ), + ] + } + + func getForecast(locationId: Int) async throws -> WeatherForecastResponse { + idForecastRequests += 1 + throw PackRatError.httpError(statusCode: 500, message: "WeatherAPI HTTP 500") + } + + func getForecast(query: String) async throws -> WeatherForecastResponse { + nameForecastQueries.append(query) + return WeatherForecastResponse( + location: WeatherResponseLocation( + id: 5419384, + name: "Denver", + region: "Colorado", + country: "United States", + lat: 39.74, + lon: -104.98, + localtime: nil, + localtimeEpoch: nil, + tzId: nil + ), + current: WeatherCurrent( + tempC: 22, + tempF: 72, + feelslikeC: 22, + feelslikeF: 72, + humidity: 32, + windMph: 7, + windKph: 11, + windDir: "WSW", + condition: WeatherCondition(text: "Partly cloudy", icon: nil, code: 1003), + uv: 5, + visMiles: 10, + precipIn: 0, + cloud: 25, + isDay: 1 + ), + forecast: WeatherForecast(forecastday: []), + alerts: WeatherAlertsWrapper(alert: []) + ) + } } // MARK: - CatalogViewModel diff --git a/apps/swift/project.yml b/apps/swift/project.yml index ead6409ee9..dd70f4b049 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -146,8 +146,8 @@ targets: settings: base: SWIFT_VERSION: "5.9" - MARKETING_VERSION: "2.0.29" - CURRENT_PROJECT_VERSION: "2026071601" + MARKETING_VERSION: "2.1.0" + CURRENT_PROJECT_VERSION: "2026072101" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU PACKRAT_IOS_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift @@ -185,8 +185,8 @@ targets: settings: base: SWIFT_VERSION: "5.9" - MARKETING_VERSION: "2.0.29" - CURRENT_PROJECT_VERSION: "2026071601" + MARKETING_VERSION: "2.1.0" + CURRENT_PROJECT_VERSION: "2026072101" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU PACKRAT_WATCH_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift.watchkitapp @@ -247,8 +247,8 @@ targets: settings: base: SWIFT_VERSION: "5.9" - MARKETING_VERSION: "2.0.29" - CURRENT_PROJECT_VERSION: "2026071601" + MARKETING_VERSION: "2.1.0" + CURRENT_PROJECT_VERSION: "2026072101" CODE_SIGN_STYLE: Manual DEVELOPMENT_TEAM: "" CODE_SIGN_IDENTITY: "-" diff --git a/apps/swift/scripts/__tests__/testflight-config.test.ts b/apps/swift/scripts/__tests__/testflight-config.test.ts index f42411c6b8..f128f94aa4 100644 --- a/apps/swift/scripts/__tests__/testflight-config.test.ts +++ b/apps/swift/scripts/__tests__/testflight-config.test.ts @@ -38,6 +38,7 @@ describe('parseTestFlightUploadConfig', () => { watchBundleId: 'com.andrewbierman.packrat.swift.watchkitapp', companionBundleId: 'com.andrewbierman.packrat.swift', displayName: 'PackRat Swift', + marketingVersion: '2.1.0', buildNumber: '123', apiEnvironment: 'production', }); @@ -59,10 +60,12 @@ describe('parseTestFlightUploadConfig', () => { watchBundleId: 'com.andrewbierman.packrat.watchkitapp', companionBundleId: 'com.andrewbierman.packrat', displayName: 'PackRat', + marketingVersion: '2.1.0', buildNumber: '456', apiEnvironment: 'production', }); expect(xcodeArchiveOverrides({ config, teamId: 'TEAM123' })).toEqual([ + 'MARKETING_VERSION=2.1.0', 'CURRENT_PROJECT_VERSION=456', 'DEVELOPMENT_TEAM=TEAM123', 'PACKRAT_IOS_BUNDLE_IDENTIFIER=com.andrewbierman.packrat', @@ -103,11 +106,24 @@ describe('parseTestFlightUploadConfig', () => { dryRun: true, bundleId: 'com.andrewbierman.packrat', displayName: 'PackRat', + marketingVersion: '2.1.0', apiEnvironment: 'production', buildNumber: '101', }); }); + it('allows an explicit marketing version override for controlled release testing', () => { + const config = parseTestFlightUploadConfig({ + argv: ['--replacement', '--production'], + env: { BUILD_NUMBER: '2026072101', MARKETING_VERSION: '2.1.1' }, + }); + + expect(config.marketingVersion).toBe('2.1.1'); + expect(xcodeArchiveOverrides({ config, teamId: 'TEAM123' })).toContain( + 'MARKETING_VERSION=2.1.1', + ); + }); + it('rejects conflicting API profile flags', () => { expect(() => parseTestFlightUploadConfig({ argv: ['--replacement', '--staging', '--production'] }), diff --git a/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts index 7a2bcc133e..fb557d18a4 100644 --- a/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts +++ b/apps/swift/scripts/__tests__/testflight-upload-cli.test.ts @@ -20,10 +20,12 @@ describe('upload-testflight CLI', () => { lane: 'replacement', bundleId: 'com.andrewbierman.packrat', displayName: 'PackRat', + marketingVersion: '2.1.0', buildNumber: '2026071801', apiEnvironment: 'production', ascProvider: 'PackRatProvider', }); + expect(preflight.archiveOverrides).toContain('MARKETING_VERSION=2.1.0'); expect(preflight.archiveOverrides).toContain('CURRENT_PROJECT_VERSION=2026071801'); }); @@ -44,6 +46,7 @@ describe('upload-testflight CLI', () => { bundleId: 'com.andrewbierman.packrat', displayName: 'PackRat', apiEnvironment: 'production', + marketingVersion: '2.1.0', buildNumber: '2026071802', currentAppStoreBuildNumber: '2026071801', ok: true, diff --git a/apps/swift/scripts/lib/testflight-config.ts b/apps/swift/scripts/lib/testflight-config.ts index 4948ec4ef4..76d75dd565 100644 --- a/apps/swift/scripts/lib/testflight-config.ts +++ b/apps/swift/scripts/lib/testflight-config.ts @@ -10,6 +10,7 @@ export type TestFlightUploadConfig = { watchBundleId: string; companionBundleId: string; displayName: string; + marketingVersion: string; buildNumber: string; apiEnvironment: 'dev' | 'production'; }; @@ -37,10 +38,11 @@ const SIDE_BY_SIDE_BUNDLE_ID = 'com.andrewbierman.packrat.swift'; const REPLACEMENT_BUNDLE_ID = 'com.andrewbierman.packrat'; const SIDE_BY_SIDE_WATCH_BUNDLE_ID = 'com.andrewbierman.packrat.swift.watchkitapp'; const REPLACEMENT_WATCH_BUNDLE_ID = 'com.andrewbierman.packrat.watchkitapp'; +const SWIFT_MARKETING_VERSION = '2.1.0'; export function parseTestFlightUploadConfig(input: { argv: readonly string[]; - env?: { BUILD_NUMBER?: string | undefined }; + env?: { BUILD_NUMBER?: string | undefined; MARKETING_VERSION?: string | undefined }; }): TestFlightUploadConfig { const { argv, env = {} } = input; const sideBySide = argv.includes('--side-by-side'); @@ -59,6 +61,7 @@ export function parseTestFlightUploadConfig(input: { } const lane: TestFlightLane = replacement ? 'replacement' : 'side-by-side'; + const marketingVersion = env.MARKETING_VERSION ?? SWIFT_MARKETING_VERSION; const buildNumber = env.BUILD_NUMBER ?? String(Math.floor(Date.now() / 1000)); return { @@ -71,6 +74,7 @@ export function parseTestFlightUploadConfig(input: { watchBundleId: replacement ? REPLACEMENT_WATCH_BUNDLE_ID : SIDE_BY_SIDE_WATCH_BUNDLE_ID, companionBundleId: replacement ? REPLACEMENT_BUNDLE_ID : SIDE_BY_SIDE_BUNDLE_ID, displayName: replacement ? 'PackRat' : 'PackRat Swift', + marketingVersion, buildNumber, apiEnvironment: staging ? 'dev' : 'production', }; @@ -82,6 +86,7 @@ export function xcodeArchiveOverrides(input: { }): string[] { const { config, teamId } = input; return [ + `MARKETING_VERSION=${config.marketingVersion}`, `CURRENT_PROJECT_VERSION=${config.buildNumber}`, `DEVELOPMENT_TEAM=${teamId}`, `PACKRAT_IOS_BUNDLE_IDENTIFIER=${config.bundleId}`, diff --git a/apps/swift/scripts/upload-testflight.ts b/apps/swift/scripts/upload-testflight.ts index 0e59ce7c1b..3c7f2b5c14 100644 --- a/apps/swift/scripts/upload-testflight.ts +++ b/apps/swift/scripts/upload-testflight.ts @@ -21,6 +21,8 @@ * APPLE_ASC_PROVIDER App Store Connect provider short name for altool; * defaults to APPLE_TEAM_ID when omitted * BUILD_NUMBER CFBundleVersion for this upload (default: timestamp) + * MARKETING_VERSION CFBundleShortVersionString for this upload + * (default: Swift minor bump 2.1.0) * APP_STORE_CURRENT_BUILD_NUMBER * Required for --replacement uploads; latest existing * PackRat App Store/TestFlight build number. @@ -86,7 +88,7 @@ let uploadConfig: TestFlightUploadConfig; try { uploadConfig = parseTestFlightUploadConfig({ argv: process.argv.slice(2), - env: { BUILD_NUMBER: nodeEnv.BUILD_NUMBER }, + env: { BUILD_NUMBER: nodeEnv.BUILD_NUMBER, MARKETING_VERSION: nodeEnv.MARKETING_VERSION }, }); } catch (error) { if (error instanceof TestFlightConfigError) { @@ -117,6 +119,7 @@ function printPreflight(input: { scheme: config.scheme, configuration: config.configuration, apiEnvironment: config.apiEnvironment, + marketingVersion: config.marketingVersion, buildNumber: config.buildNumber, ascProvider, archiveOverrides, diff --git a/apps/swift/scripts/verify-testflight-replacement.ts b/apps/swift/scripts/verify-testflight-replacement.ts index 01d4d2e6b2..03fbd4c41b 100644 --- a/apps/swift/scripts/verify-testflight-replacement.ts +++ b/apps/swift/scripts/verify-testflight-replacement.ts @@ -38,7 +38,7 @@ if (HELP) { try { const config = parseTestFlightUploadConfig({ argv: process.argv.slice(2), - env: { BUILD_NUMBER: nodeEnv.BUILD_NUMBER }, + env: { BUILD_NUMBER: nodeEnv.BUILD_NUMBER, MARKETING_VERSION: nodeEnv.MARKETING_VERSION }, }); const readiness = verifyTestFlightReplacementReadiness({ config, @@ -53,6 +53,7 @@ try { displayName: config.displayName, configuration: config.configuration, apiEnvironment: config.apiEnvironment, + marketingVersion: config.marketingVersion, buildNumber: config.buildNumber, currentAppStoreBuildNumber: nodeEnv.APP_STORE_CURRENT_BUILD_NUMBER ?? null, ok: readiness.ok, From 2bd180cc349103dc0ffa8d37950e2dd992d5fd6e Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Tue, 21 Jul 2026 07:19:36 -0600 Subject: [PATCH 70/81] =?UTF-8?q?=F0=9F=A7=AA=20sync=20Swift=20feature=20f?= =?UTF-8?q?lag=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/Sources/PackRat/Config/AppFeatureFlags.swift | 1 + apps/swift/Tests/PackRatUITests/UITestFeatureFlags.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/swift/Sources/PackRat/Config/AppFeatureFlags.swift b/apps/swift/Sources/PackRat/Config/AppFeatureFlags.swift index 895e99834d..cf0f2600dd 100644 --- a/apps/swift/Sources/PackRat/Config/AppFeatureFlags.swift +++ b/apps/swift/Sources/PackRat/Config/AppFeatureFlags.swift @@ -9,6 +9,7 @@ enum AppFeatureFlags { static let enableOAuth = true static let enablePackInsights = false static let enablePackTemplates = true + static let enableRevenueCat = true static let enableSharedPacks = false static let enableShoppingList = false static let enableTrailConditions = true diff --git a/apps/swift/Tests/PackRatUITests/UITestFeatureFlags.swift b/apps/swift/Tests/PackRatUITests/UITestFeatureFlags.swift index 5a99282fef..91fa38b6d9 100644 --- a/apps/swift/Tests/PackRatUITests/UITestFeatureFlags.swift +++ b/apps/swift/Tests/PackRatUITests/UITestFeatureFlags.swift @@ -9,6 +9,7 @@ enum UITestFeatureFlags { static let enableOAuth = true static let enablePackInsights = false static let enablePackTemplates = true + static let enableRevenueCat = true static let enableSharedPacks = false static let enableShoppingList = false static let enableTrailConditions = true From 6b167e1901a452d51d7abf562e21763526505ce5 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Thu, 23 Jul 2026 08:19:28 -0600 Subject: [PATCH 71/81] fix(swift): address tester navigation and units feedback --- .../GearInventory/GearInventoryView.swift | 22 +++++++- .../PackRat/Features/Home/HomeView.swift | 10 +++- .../Features/Weather/ForecastRow.swift | 38 +++++++++++-- .../Features/Weather/WeatherView.swift | 29 ++++++---- .../PackRat/Navigation/AppNavigation.swift | 9 ++- .../Tests/PackRatTests/ViewModelTests.swift | 28 ++++++++++ .../Tests/PackRatUITests/MoreTabsTests.swift | 35 ++++++++++++ .../Tests/PackRatUITests/WeatherTests.swift | 44 ++++++++++++++- ...-07-23-001-fix-swift-tester-issues-plan.md | 56 +++++++++++++++++++ 9 files changed, 250 insertions(+), 21 deletions(-) create mode 100644 docs/plans/2026-07-23-001-fix-swift-tester-issues-plan.md diff --git a/apps/swift/Sources/PackRat/Features/GearInventory/GearInventoryView.swift b/apps/swift/Sources/PackRat/Features/GearInventory/GearInventoryView.swift index f53ea6a802..2839a222a4 100644 --- a/apps/swift/Sources/PackRat/Features/GearInventory/GearInventoryView.swift +++ b/apps/swift/Sources/PackRat/Features/GearInventory/GearInventoryView.swift @@ -75,12 +75,28 @@ struct GearInventoryView: View { .searchable(text: $searchText, prompt: "Search gear") .toolbar { ToolbarItem(placement: .primaryAction) { - Picker("Sort", selection: $sortOrder) { + Menu { ForEach(SortOrder.allCases, id: \.self) { order in - Text(order.rawValue).tag(order) + Button { + sortOrder = order + } label: { + Label( + order.rawValue, + systemImage: order == sortOrder ? "checkmark" : "circle" + ) + } } + } label: { + HStack(spacing: 4) { + Text(sortOrder.rawValue) + Image(systemName: "arrow.up.arrow.down") + } + .font(.subheadline) + .fixedSize() } - .pickerStyle(.menu) + .accessibilityIdentifier("gear_inventory_sort") + .accessibilityLabel("Sort gear inventory") + .accessibilityValue(sortOrder.rawValue) } } .task { await appState.packsVM.load(context: modelContext) } diff --git a/apps/swift/Sources/PackRat/Features/Home/HomeView.swift b/apps/swift/Sources/PackRat/Features/Home/HomeView.swift index 136461bfa6..b760ac6a99 100644 --- a/apps/swift/Sources/PackRat/Features/Home/HomeView.swift +++ b/apps/swift/Sources/PackRat/Features/Home/HomeView.swift @@ -1,6 +1,8 @@ import SwiftUI struct HomeView: View { + var onOpenAssistant: (() -> Void)? + @Environment(AppState.self) private var appState @Environment(AuthManager.self) private var authManager @Environment(\.horizontalSizeClass) private var horizontalSizeClass @@ -306,7 +308,13 @@ struct HomeView: View { ) { appState.navItem = .packs }, HomeAction(title: "Trips", subtitle: upcomingTripsSubtitle, symbol: "map.fill", color: .green) { appState.navItem = .trips }, HomeAction(title: "Weather", subtitle: "Forecasts & alerts", symbol: "cloud.sun.fill", color: .cyan) { appState.navItem = .weather }, - HomeAction(title: "AI Assistant", subtitle: "Ask about gear & trips", symbol: "bubble.left.and.text.bubble.right", color: .purple) { appState.navItem = .chat }, + HomeAction(title: "AI Assistant", subtitle: "Ask about gear & trips", symbol: "bubble.left.and.text.bubble.right", color: .purple) { + if let onOpenAssistant { + onOpenAssistant() + } else { + appState.navItem = .chat + } + }, HomeAction(title: "AI Packs", subtitle: "Generate pack ideas", symbol: "sparkles", color: .purple) { appState.navItem = .aiPacks }, HomeAction(title: "Gear Inventory", subtitle: inventorySubtitle, symbol: "shippingbox.fill", color: .orange) { appState.navItem = .gearInventory }, HomeAction(title: "Season Suggestions", subtitle: "AI-powered packing tips", symbol: "leaf.fill", color: .mint) { showingSeasonSuggestions = true }, diff --git a/apps/swift/Sources/PackRat/Features/Weather/ForecastRow.swift b/apps/swift/Sources/PackRat/Features/Weather/ForecastRow.swift index 805427ab69..559acd35f3 100644 --- a/apps/swift/Sources/PackRat/Features/Weather/ForecastRow.swift +++ b/apps/swift/Sources/PackRat/Features/Weather/ForecastRow.swift @@ -1,7 +1,27 @@ import SwiftUI +enum WeatherTemperatureDisplay { + static func format( + celsius: Double?, + fahrenheit: Double?, + unit: AppPreferences.TemperatureUnit + ) -> String { + let value: Double? + switch unit { + case .celsius: + value = celsius ?? fahrenheit.map { ($0 - 32) * 5 / 9 } + case .fahrenheit: + value = fahrenheit ?? celsius.map { ($0 * 9 / 5) + 32 } + } + + guard let value else { return "—" } + return "\(Int(value.rounded()))\(unit.label)" + } +} + struct ForecastRow: View { let day: ForecastDay + let temperatureUnit: AppPreferences.TemperatureUnit var body: some View { HStack(spacing: 12) { @@ -33,14 +53,24 @@ struct ForecastRow: View { .foregroundStyle(.blue) } - Text(String(format: "%.0f°", day.day?.maxtempF ?? 0)) + Text(WeatherTemperatureDisplay.format( + celsius: day.day?.maxtempC, + fahrenheit: day.day?.maxtempF, + unit: temperatureUnit + )) .font(.callout.bold()) - .frame(width: 36, alignment: .trailing) + .frame(width: 44, alignment: .trailing) + .accessibilityIdentifier("weather_forecast_high_\(day.id)") - Text(String(format: "%.0f°", day.day?.mintempF ?? 0)) + Text(WeatherTemperatureDisplay.format( + celsius: day.day?.mintempC, + fahrenheit: day.day?.mintempF, + unit: temperatureUnit + )) .font(.callout) .foregroundStyle(.secondary) - .frame(width: 36, alignment: .trailing) + .frame(width: 44, alignment: .trailing) + .accessibilityIdentifier("weather_forecast_low_\(day.id)") } } .padding(.horizontal) diff --git a/apps/swift/Sources/PackRat/Features/Weather/WeatherView.swift b/apps/swift/Sources/PackRat/Features/Weather/WeatherView.swift index 2b76746e4c..43773fe477 100644 --- a/apps/swift/Sources/PackRat/Features/Weather/WeatherView.swift +++ b/apps/swift/Sources/PackRat/Features/Weather/WeatherView.swift @@ -6,6 +6,7 @@ struct WeatherView: View { @State private var showingAlerts = false @State private var showingAlertPreferences = false @State private var isSearchPresented = false + @AppStorage("temperatureUnit") private var temperatureUnit: AppPreferences.TemperatureUnit = .fahrenheit @AppStorage("speedUnit") private var speedUnit: SpeedUnit = .mph /// Renders an API wind value (always mph) in the user's preferred unit. @@ -257,7 +258,7 @@ struct WeatherView: View { .padding(.horizontal, 4) VStack(spacing: 0) { ForEach(days) { day in - ForecastRow(day: day) + ForecastRow(day: day, temperatureUnit: temperatureUnit) if day.id != days.last?.id { Divider().padding(.horizontal) } @@ -285,14 +286,13 @@ struct WeatherView: View { .symbolRenderingMode(.multicolor) } - HStack(alignment: .lastTextBaseline, spacing: 4) { - Text(String(format: "%.0f°", current.tempF ?? 0)) - .font(.system(size: 64, weight: .thin)) - Text("F") - .font(.title3) - .foregroundStyle(.secondary) - .padding(.bottom, 8) - } + Text(WeatherTemperatureDisplay.format( + celsius: current.tempC, + fahrenheit: current.tempF, + unit: temperatureUnit + )) + .font(.system(size: 64, weight: .thin)) + .accessibilityIdentifier("weather_current_temperature") if let condition = current.condition?.text { Text(condition) @@ -303,7 +303,16 @@ struct WeatherView: View { Divider() HStack(spacing: 0) { - weatherDetail("Feels Like", value: String(format: "%.0f°", current.feelslikeF ?? 0), symbol: "thermometer") + weatherDetail( + "Feels Like", + value: WeatherTemperatureDisplay.format( + celsius: current.feelslikeC, + fahrenheit: current.feelslikeF, + unit: temperatureUnit + ), + symbol: "thermometer" + ) + .accessibilityIdentifier("weather_feels_like_temperature") Divider().frame(height: 32) weatherDetail("Humidity", value: "\(current.humidity ?? 0)%", symbol: "humidity") Divider().frame(height: 32) diff --git a/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift b/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift index 089e8e10a5..a15a8b60c4 100644 --- a/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift +++ b/apps/swift/Sources/PackRat/Navigation/AppNavigation.swift @@ -310,7 +310,10 @@ struct AppNavigation: View { return TabView(selection: $phoneTab) { NavigationStack(path: $phoneHomePath) { - phoneContentView(.home) + HomeView { + phoneHomePath.append(.chat) + } + .environment(appState) .navigationTitle(NavItem.home.label) .navigationDestination(for: NavItem.self) { item in phoneContentView(item) @@ -364,7 +367,9 @@ struct AppNavigation: View { } .onChange(of: phoneHomePath) { _, path in if let item = path.last { - state.navItem = item + if item != .chat { + state.navItem = item + } } else if phoneTab == .home { state.navItem = .home } diff --git a/apps/swift/Tests/PackRatTests/ViewModelTests.swift b/apps/swift/Tests/PackRatTests/ViewModelTests.swift index 2b87351bd3..69475d1ee5 100644 --- a/apps/swift/Tests/PackRatTests/ViewModelTests.swift +++ b/apps/swift/Tests/PackRatTests/ViewModelTests.swift @@ -25,6 +25,34 @@ private func mockTrip(id: String = "t1", name: String = "Test Trip", startDate: deleted: false, createdAt: nil, updatedAt: nil) } +@Suite("WeatherTemperatureDisplay") +struct WeatherTemperatureDisplayTests { + @Test("uses Celsius value when Celsius is preferred") + func usesPreferredCelsiusValue() { + #expect(WeatherTemperatureDisplay.format(celsius: 20, fahrenheit: 99, unit: .celsius) == "20°C") + } + + @Test("uses Fahrenheit value when Fahrenheit is preferred") + func usesPreferredFahrenheitValue() { + #expect(WeatherTemperatureDisplay.format(celsius: 99, fahrenheit: 68, unit: .fahrenheit) == "68°F") + } + + @Test("converts Fahrenheit fallback to Celsius") + func convertsFahrenheitFallback() { + #expect(WeatherTemperatureDisplay.format(celsius: nil, fahrenheit: 68, unit: .celsius) == "20°C") + } + + @Test("converts Celsius fallback to Fahrenheit") + func convertsCelsiusFallback() { + #expect(WeatherTemperatureDisplay.format(celsius: 20, fahrenheit: nil, unit: .fahrenheit) == "68°F") + } + + @Test("shows placeholder only when both values are unavailable") + func showsMissingPlaceholder() { + #expect(WeatherTemperatureDisplay.format(celsius: nil, fahrenheit: nil, unit: .celsius) == "—") + } +} + // MARK: - PacksViewModel @Suite("PacksViewModel") diff --git a/apps/swift/Tests/PackRatUITests/MoreTabsTests.swift b/apps/swift/Tests/PackRatUITests/MoreTabsTests.swift index b126c71cb0..6e2913d71b 100644 --- a/apps/swift/Tests/PackRatUITests/MoreTabsTests.swift +++ b/apps/swift/Tests/PackRatUITests/MoreTabsTests.swift @@ -78,6 +78,25 @@ final class MoreTabsTests: AppUITestCase { ) } + func testHomeAssistantActionProvidesBackNavigationWithoutChangingDirectTabHistory() { + goToHomeAction("AI Assistant") + XCTAssertTrue(app.navigationBars["AI Assistant"].waitForExistence(timeout: 8)) + XCTAssertTrue( + app.navigationBars["AI Assistant"].buttons["Home"].waitForExistence(timeout: 5), + "Assistant opened from Home should offer native back navigation" + ) + + app.navigationBars["AI Assistant"].buttons["Home"].tap() + XCTAssertTrue(app.navigationBars["Home"].waitForExistence(timeout: 8)) + + goToTab("Assistant") + XCTAssertTrue(app.navigationBars["AI Assistant"].waitForExistence(timeout: 8)) + XCTAssertFalse( + app.navigationBars["AI Assistant"].buttons["Home"].exists, + "The primary Assistant tab should remain a root destination" + ) + } + // MARK: - Guides func testGuidesTabReachable() { @@ -98,6 +117,22 @@ final class MoreTabsTests: AppUITestCase { ) } + func testGearInventorySortMenuExposesOptionsAndUpdatesSelectedValue() { + goToHomeAction("Gear Inventory") + XCTAssertTrue(app.navigationBars["Gear Inventory"].waitForExistence(timeout: 8)) + + let sortMenu = app.buttons["gear_inventory_sort"] + XCTAssertTrue(sortMenu.waitForExistence(timeout: 8)) + XCTAssertEqual(sortMenu.value as? String, "Name") + + sortMenu.tap() + let weight = app.buttons["Weight"] + XCTAssertTrue(weight.waitForExistence(timeout: 5)) + weight.tap() + + XCTAssertEqual(sortMenu.value as? String, "Weight") + } + // MARK: - Wildlife func testDisabledWildlifeActionIsHidden() { diff --git a/apps/swift/Tests/PackRatUITests/WeatherTests.swift b/apps/swift/Tests/PackRatUITests/WeatherTests.swift index c646444433..9e81a1091a 100644 --- a/apps/swift/Tests/PackRatUITests/WeatherTests.swift +++ b/apps/swift/Tests/PackRatUITests/WeatherTests.swift @@ -5,7 +5,9 @@ import XCTest /// End-to-end tests for Weather: location search, forecast display, saved locations. final class WeatherTests: AppUITestCase { - override var additionalLaunchArguments: [String] { ["--ui-test-fixtures"] } + override var additionalLaunchArguments: [String] { + ["--ui-test-fixtures", "-temperatureUnit", "°C"] + } private let testCity = "Denver" private let testCityFull = "Denver" // fragment to match in search results @@ -128,6 +130,32 @@ final class WeatherTests: AppUITestCase { ) } + func testCelsiusPreferenceAppliesToCurrentFeelsLikeAndForecast() { + goToWeather() + + let searchField = app.searchFields["Search locations\u{2026}"] + waitFor(searchField) + searchField.tap() + searchField.typeText(testCity) + + let firstResult = weatherSearchResults().firstMatch + waitFor(firstResult, timeout: weatherSearchTimeout) + firstResult.tap() + + let currentCard = app.descendants(matching: .any)["weather_current_card"] + XCTAssertTrue(currentCard.waitForExistence(timeout: 20)) + assertCelsiusValue(identifier: "weather_current_temperature") + assertCelsiusValue(identifier: "weather_feels_like_temperature") + + let list = app.collectionViews.firstMatch + for _ in 0..<4 where !app.staticTexts["10-Day Forecast"].exists { + list.swipeUp() + } + XCTAssertTrue(app.staticTexts["10-Day Forecast"].waitForExistence(timeout: 20)) + assertCelsiusValue(identifierPrefix: "weather_forecast_high_") + assertCelsiusValue(identifierPrefix: "weather_forecast_low_") + } + func testWeatherAlertsButtonAppearsWithForecast() { goToWeather() @@ -164,6 +192,20 @@ final class WeatherTests: AppUITestCase { NSPredicate(format: "identifier BEGINSWITH 'weather_search_result_' AND label CONTAINS '\(testCityFull)'") ) } + + private func assertCelsiusValue(identifier: String) { + let value = app.descendants(matching: .any)[identifier] + XCTAssertTrue(value.waitForExistence(timeout: 5)) + XCTAssertTrue(value.label.hasSuffix("°C"), "\(identifier) should use Celsius") + } + + private func assertCelsiusValue(identifierPrefix: String) { + let value = app.descendants(matching: .any).matching( + NSPredicate(format: "identifier BEGINSWITH %@", identifierPrefix) + ).firstMatch + XCTAssertTrue(value.waitForExistence(timeout: 5)) + XCTAssertTrue(value.label.hasSuffix("°C"), "\(identifierPrefix) values should use Celsius") + } } #endif diff --git a/docs/plans/2026-07-23-001-fix-swift-tester-issues-plan.md b/docs/plans/2026-07-23-001-fix-swift-tester-issues-plan.md new file mode 100644 index 0000000000..aa52fc98e4 --- /dev/null +++ b/docs/plans/2026-07-23-001-fix-swift-tester-issues-plan.md @@ -0,0 +1,56 @@ +--- +title: Fix Swift tester issues 2640-2642 +date: 2026-07-23 +artifact_contract: ce-unified-plan/v1 +artifact_readiness: implementation-ready +product_contract_source: ce-plan-bootstrap +execution: code +--- + +# Fix Swift tester issues 2640-2642 + +## Product Contract + +### Requirements + +- R1: Opening AI Assistant from Home must create native back navigation to Home. +- R2: Opening the primary Assistant tab must remain a root destination without synthetic history. +- R3: Gear Inventory sorting must use a balanced native menu that exposes and updates the selected order accessibly. +- R4: Weather current, feels-like, forecast-high, and forecast-low temperatures must honor the saved Celsius/Fahrenheit preference. +- R5: Weather formatting must prefer the API field matching the selected unit, convert the alternate field when necessary, and show a placeholder only when both are unavailable. + +### Acceptance Evidence + +- AE1: A focused UI test covers Home-origin Assistant back navigation and direct-tab history. +- AE2: A focused UI test covers the Gear sort menu options and selected accessibility value. +- AE3: Unit tests cover both preferred fields, both conversion fallbacks, and missing data. +- AE4: A focused UI test addresses all four Weather temperature surfaces independently. +- AE5: The PackRat iOS simulator target builds successfully. + +## Key Technical Decisions + +- KTD1: Home accepts an optional Assistant-opening callback; the compact iPhone Home stack supplies it, while all existing Home callers retain global navigation behavior. +- KTD2: The Home stack does not mirror its pushed `.chat` route into global tab selection, preventing the tab observer from erasing native back history. +- KTD3: Gear sorting uses an explicit `Menu` with a current-value label, selection indicator, and accessibility value. +- KTD4: A pure formatter centralizes preferred-field selection, fallback conversion, rounding, suffixes, and missing-value behavior. + +## Implementation Units + +### U1: Source-sensitive Assistant navigation + +Implements R1-R2 and AE1 using `HomeView`, `AppNavigation`, and `MoreTabsTests`. + +### U2: Explicit Gear sorting menu + +Implements R3 and AE2 using `GearInventoryView` and `MoreTabsTests`. + +### U3: Preference-aware Weather temperatures + +Implements R4-R5 and AE3-AE4 using `ForecastRow`, `WeatherView`, `ViewModelTests`, and `WeatherTests`. + +## Verification + +- Generate the Xcode project from `apps/swift/project.yml`. +- Build `PackRat-iOS` for an available iOS simulator through XcodeBuildMCP. +- Run the five `WeatherTemperatureDisplayTests`. +- Run the three focused UI tests when PackRat E2E credentials are available; otherwise record an explicit credential-based skip rather than claiming execution. From cc2bc691e6c9ff8c87931b7873af1cf1cf870c80 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:46:31 -0600 Subject: [PATCH 72/81] fix(ci): use dev API for scheduled Swift E2E --- .github/workflows/swift-e2e.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift-e2e.yml b/.github/workflows/swift-e2e.yml index f614dc82b8..75c7435602 100644 --- a/.github/workflows/swift-e2e.yml +++ b/.github/workflows/swift-e2e.yml @@ -211,6 +211,9 @@ jobs: - name: Resolve Swift E2E target run: | target="${{ inputs.api_environment || 'configured-secret' }}" + if [ "${{ github.event_name }}" = "schedule" ]; then + target="dev" + fi case "$target" in configured-secret) echo "PACKRAT_ENV=dev" >> "$GITHUB_ENV" @@ -233,6 +236,9 @@ jobs: - name: Verify Swift E2E secrets run: | target="${{ inputs.api_environment || 'configured-secret' }}" + if [ "${{ github.event_name }}" = "schedule" ]; then + target="dev" + fi missing=() if [ "$target" != "local" ]; then [ -z "${E2E_EMAIL:-}" ] && missing+=("E2E_TEST_EMAIL") @@ -348,7 +354,8 @@ jobs: - name: Resolve Swift E2E target run: | target="${{ inputs.api_environment || 'configured-secret' }}" - if [ "${{ github.event_name }}" = "pull_request" ]; then + if [ "${{ github.event_name }}" = "pull_request" ] || + [ "${{ github.event_name }}" = "schedule" ]; then target="dev" fi case "$target" in @@ -373,7 +380,8 @@ jobs: - name: Verify Swift E2E secrets run: | target="${{ inputs.api_environment || 'configured-secret' }}" - if [ "${{ github.event_name }}" = "pull_request" ]; then + if [ "${{ github.event_name }}" = "pull_request" ] || + [ "${{ github.event_name }}" = "schedule" ]; then target="dev" fi missing=() From 766b7261a0393b05fe8b4bf74eebb9ac51f8c2c4 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:39:09 -0600 Subject: [PATCH 73/81] =?UTF-8?q?=F0=9F=A7=AA=20test(api):=20harden=20loca?= =?UTF-8?q?l=20e2e=20launch=20readiness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...8-06-001-swift-launch-readiness-qa-plan.md | 227 ++++++++++++++++++ packages/api/migrate.ts | 67 +++++- packages/api/scripts/e2e-local-start.ts | 3 +- packages/api/scripts/e2e-node-server.ts | 13 +- .../api/src/auth/__tests__/mcp-token.test.ts | 1 + .../src/auth/__tests__/oauth-provider.test.ts | 2 +- packages/api/src/auth/auth.config.ts | 2 +- packages/api/src/auth/consent-route.ts | 2 +- packages/api/src/auth/index.ts | 2 +- packages/api/src/auth/local-e2e.ts | 1 + packages/api/src/auth/mcp-token.ts | 2 +- packages/api/src/db/index.ts | 48 ++-- packages/api/src/db/seed-e2e-user.ts | 61 ++--- packages/api/src/e2e-worker.ts | 65 ++++- packages/api/src/index.ts | 2 +- packages/api/src/middleware/auth.ts | 8 +- .../api/src/routes/admin/analytics/catalog.ts | 2 +- .../src/routes/admin/analytics/platform.ts | 9 +- packages/api/src/routes/admin/index.ts | 9 +- packages/api/src/routes/admin/trails.ts | 2 +- packages/api/src/routes/catalog/index.ts | 2 +- packages/api/src/routes/chat.ts | 65 ++--- packages/api/src/routes/feed/index.ts | 2 +- packages/api/src/routes/guides/index.ts | 42 ++-- .../api/src/routes/knowledgeBase/reader.ts | 6 +- .../api/src/routes/packTemplates/index.ts | 4 +- packages/api/src/routes/packs/index.ts | 4 +- packages/api/src/routes/seasonSuggestions.ts | 4 +- .../api/src/routes/trailConditions/reports.ts | 4 +- packages/api/src/routes/trips/index.ts | 2 +- packages/api/src/routes/user/index.ts | 2 +- .../__tests__/entitlementsService.test.ts | 1 + .../__tests__/featureAccessService.test.ts | 5 +- .../__tests__/featureFlagsService.test.ts | 5 +- .../__tests__/passwordResetService.test.ts | 5 +- .../services/__tests__/userService.test.ts | 7 +- packages/api/src/services/aiService.ts | 2 +- packages/api/src/services/catalogService.ts | 2 +- packages/api/src/services/embeddingService.ts | 3 +- .../api/src/services/entitlementsService.ts | 2 +- .../src/services/etl/CatalogItemValidator.ts | 2 +- .../api/src/services/etl/mergeItemsBySku.ts | 2 +- .../api/src/services/etl/processCatalogEtl.ts | 2 +- .../api/src/services/etl/processLogsBatch.ts | 2 +- .../services/etl/processValidItemsBatch.ts | 2 +- .../src/services/etl/updateEtlJobProgress.ts | 2 +- .../api/src/services/featureAccessService.ts | 2 +- .../api/src/services/featureFlagsService.ts | 2 +- .../api/src/services/imageDetectionService.ts | 4 +- packages/api/src/services/packItemService.ts | 2 +- packages/api/src/services/packService.ts | 4 +- .../api/src/services/passwordResetService.ts | 5 +- .../services/retention/invalidLogRetention.ts | 2 +- packages/api/src/services/userService.ts | 2 +- .../services/wildlifeIdentificationService.ts | 2 +- packages/api/src/types/etl.ts | 2 +- packages/api/src/utils/DbUtils.ts | 2 +- .../src/utils/__tests__/compute-pack.test.ts | 2 +- .../utils/__tests__/env-validation.test.ts | 8 +- packages/api/src/utils/ai/tools.ts | 4 +- packages/api/src/utils/compute-pack.ts | 2 +- packages/api/src/utils/csv-utils.ts | 2 +- packages/api/src/utils/email.ts | 2 +- packages/api/src/utils/embeddingHelper.ts | 2 +- packages/api/src/utils/getPresignedUrl.ts | 16 +- packages/api/src/utils/json-utils.ts | 2 +- .../api/src/workflows/catalog-etl-workflow.ts | 2 +- packages/api/test/etl.test.ts | 2 +- packages/api/test/feature-access.test.ts | 2 +- .../api/test/fixtures/catalog-fixtures.ts | 2 +- packages/api/test/fixtures/pack-fixtures.ts | 2 +- .../test/fixtures/pack-template-fixtures.ts | 2 +- packages/api/test/packs.test.ts | 4 +- packages/api/test/utils/db-helpers.ts | 4 +- packages/api/test/utils/user-helpers.ts | 2 +- 75 files changed, 592 insertions(+), 208 deletions(-) create mode 100644 docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md diff --git a/docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md b/docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md new file mode 100644 index 0000000000..4dac80e9e5 --- /dev/null +++ b/docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md @@ -0,0 +1,227 @@ +--- +title: Swift Launch Readiness QA - Plan +type: fix +date: 2026-08-06 +artifact_contract: ce-unified-plan/v1 +artifact_readiness: implementation-ready +product_contract_source: ce-plan-bootstrap +execution: code +--- + +# Swift Launch Readiness QA - Plan + +## Goal Capsule + +- **Objective:** Bring the Swift iOS, iPad, macOS, and Watch surfaces to launch-readiness confidence as a seamless update path from the Expo app by syncing current branches, generating fresh screenshot evidence, running full Swift QA, and fixing any launch-blocking defects found. +- **Authority:** Expo production behavior remains the parity baseline; open Swift/mobile GitHub issues and live screenshot evidence override stale prior confidence claims; native SwiftUI/AppKit expectations govern UI polish. +- **Execution profile:** Audit latest branches and PRs, preserve existing uncommitted Swift tester fixes, run screenshot and E2E coverage across Apple targets, inspect failures/screenshots manually, fix real bugs with focused tests, then commit/PR/watch CI. +- **Stop conditions:** Stop only for unavailable credentials/signing infrastructure, a failing local toolchain that cannot build Apple targets, or a product decision needed to choose between Expo parity and a new Swift-native behavior. +- **Tail ownership:** The implementation run owns branch hygiene, QA artifacts, screenshot contact sheets, fixes, regression tests, PR updates, and CI follow-through. + +--- + +## Product Contract + +### Summary + +The Swift app should be ready for tester distribution as the Apple-native successor to the Expo mobile experience, with no known launch-blocking gaps in core flows, auth/guest behavior, offline/local-first behavior, AI/weather/catalog/pack CRUD, visual polish, or platform packaging. + +### Problem Frame + +Prior Swift readiness work improved the app substantially, but repeated tester feedback showed that passing checks did not always mean screens were visually correct or deployed/TestFlight behavior was healthy. The current request is a launch-readiness reset: inspect the latest branches, generate fresh screenshots, run real QA, and keep fixing until the Swift variant is credible as a seamless update from Expo. + +### Requirements + +#### Branch and Ticket Reality + +- R1. The working branch is synced against the latest intended base branch and all relevant open Swift/TestFlight/mobile PRs and issues are reviewed before declaring readiness. +- R2. Existing uncommitted Swift tester-fix work is preserved, understood, and either incorporated into this readiness branch or deliberately isolated with a clear reason. +- R3. PR metadata and versioning must not contradict the app binaries, TestFlight replacement settings, or Expo baseline version. + +#### Screenshots and Manual Review + +- R4. Fresh screenshot catalogs exist for iPhone, iPad, macOS, and Watch where tooling supports them. +- R5. Screenshot catalogs include guest, authenticated, offline/error, populated, modal, menu, form, and feature-specific states needed to visually detect broken UI. +- R6. The implementation run manually reviews the screenshot outputs for obvious errors, stale data confusion, missing icons, bad padding, broken empty states, overflow, connection-state misuse, and non-native-looking controls. + +#### E2E and Functional Coverage + +- R7. Swift iOS and macOS smoke/full or equivalent UI E2E suites run against the local deterministic API where possible. +- R8. Core features are testable end to end: auth/guest entry, home navigation/search, packs/trips/items CRUD, catalog add flow, templates, weather, season suggestions, trail conditions, chat/AI fallback, settings/preferences, offline/local-first behavior, and platform-specific Watch sync smoke. +- R9. Tests must verify behavior rather than hiding defects through overly broad retries, weak assertions, or fixture states that mask real deployed failures. + +#### Launch Readiness + +- R10. App Store/TestFlight replacement configuration is verified for bundle identifiers, display names, version/build numbers, icons, orientations, entitlements, and archive overrides. +- R11. Any issue found during QA is fixed with the smallest scoped change that follows existing SwiftUI patterns and includes regression coverage. +- R12. Readiness is reported honestly: green CI and screenshots are evidence, but remaining open tickets and untested deployed/TestFlight paths are called out. + +### Key Flows + +- F1. **Fresh branch audit** + - **Trigger:** Launch-readiness run starts. + - **Steps:** Fetch/prune remotes, inspect current branch, inspect open PRs/issues, compare branch against development/main as appropriate, and identify any stale or conflicting Swift work. + - **Outcome:** The active branch choice and unresolved ticket list are explicit. + - **Covered by:** R1, R2, R3. +- F2. **Screenshot-driven QA** + - **Trigger:** Swift builds and test runners are runnable. + - **Steps:** Generate screenshot catalogs for supported Apple platforms, inspect contact sheets and individual images, record defects, and fix visible regressions. + - **Outcome:** Screenshots are current review artifacts, not stale confidence theater. + - **Covered by:** R4, R5, R6, R11. +- F3. **Full functional QA** + - **Trigger:** Branch is synced and screenshot setup is understood. + - **Steps:** Run Swift scripts, unit tests, iOS/macOS E2E, Watch sync smoke, TestFlight preflight, and targeted tests for any changed flows. + - **Outcome:** Core flows have executable coverage and failures are triaged as real bugs or infrastructure blockers. + - **Covered by:** R7, R8, R9, R10. + +### Acceptance Examples + +- AE1. Given the current branch has stale or uncommitted Swift tester work, when launch QA begins, then that work is listed, preserved, and either validated or deliberately separated before commits are made. +- AE2. Given a fresh iPhone screenshot catalog, when a Home, form, modal, menu, Weather, Chat, Pack, Catalog, Settings, or offline state is visually broken, then a concrete issue is fixed or recorded before readiness is claimed. +- AE3. Given the Swift app is intended to replace the Expo listing, when TestFlight preflight runs, then replacement bundle IDs, version/build, archive overrides, and display metadata align with that release path. +- AE4. Given the local deterministic API is available, when iOS and macOS E2E flows run, then auth/guest and core CRUD/AI/weather flows are asserted with feature-specific expectations. +- AE5. Given some repo-wide tickets remain outside this Swift launch scope, when the final report is written, then they are named as residuals rather than implied solved. + +### Scope Boundaries + +- Expo implementation changes are out of scope unless they directly unblock Swift parity verification or shared API stubs. +- Broad API security tickets and unrelated web/landing issues are not launch blockers for this Swift readiness pass unless QA proves they affect Swift runtime behavior. +- Dummy data should support deterministic tests and screenshots only; it must not create confusing production user-facing defaults. +- A real TestFlight/device smoke is required for final release confidence, but this plan can only automate it when credentials, signing, and App Store Connect access are locally available. + +### Sources + +- Open PRs #2627 and #2653, plus any newer Swift/mobile PRs discovered during implementation. +- Existing plans `docs/plans/2026-07-17-001-fix-swift-testflight-loading-and-pr-readiness-plan.md` and `docs/plans/2026-07-23-001-fix-swift-tester-issues-plan.md`. +- Swift app sources under `apps/swift/Sources/PackRat`, Swift tests under `apps/swift/Tests`, and screenshot/E2E scripts under `apps/swift/scripts`. + +--- + +## Planning Contract + +### Key Technical Decisions + +- KTD1. Use screenshot evidence as a release gate, not only test pass/fail. (session-settled: user-directed - chosen over test-only confidence: prior iterations missed visible error states and broken UI despite passing tests.) +- KTD2. Treat Expo as the parity baseline while allowing Swift-native UI improvements. (session-settled: user-directed - chosen over a standalone Swift redesign: the Swift app is intended to ship as a seamless update to the existing Expo app.) +- KTD3. Preserve and audit existing uncommitted tester fixes before editing. Uncommitted changes in this worktree are assumed to be user/prior-agent work and must not be overwritten or casually folded into unrelated commits. +- KTD4. Prefer deterministic local API and generated fixtures for E2E reliability, but do not weaken assertions or hide deployed/TestFlight bugs. Local stubs prove app behavior; deployed smoke remains a separate release gate. +- KTD5. Keep launch-readiness fixes small and SwiftUI-native. Reusable components are appropriate for repeated state/layout patterns, but unrelated refactors wait until after launch-readiness evidence is clean. + +### High-Level Technical Design + +```mermaid +flowchart TB + BranchAudit[Branch and issue audit] --> ExistingDiff[Classify existing Swift diff] + ExistingDiff --> Build[Generate project and build] + Build --> Tests[Run unit, scripts, iOS, macOS, Watch checks] + Tests --> Screens[Capture screenshots by platform and state] + Screens --> Review[Manual visual review] + Review --> Bugs{Launch blockers?} + Bugs -->|yes| Fix[Fix with regression tests] + Fix --> Tests + Bugs -->|no| Ship[Commit, PR update, CI watch] +``` + +### Assumptions + +- The intended integration base remains `development` unless branch/PR metadata shows the Swift launch branch has moved elsewhere. +- The local machine has Xcode, simulators, and Apple tooling installed, but signing/TestFlight upload may still require credentials or manual Apple account state. +- The current worktree may be stale relative to GitHub because older fetches can hang; implementation should verify remote state with targeted GitHub commands when needed. + +### Sequencing + +Start by making branch and diff state explicit, because existing uncommitted Swift changes change both QA scope and commit hygiene. Then run cheap script/unit checks before expensive simulator screenshot/E2E runs. Use screenshot review to drive targeted fixes, then rerun only the affected narrow tests before final broad gates. + +--- + +## Implementation Units + +### U1. Branch, PR, and issue audit + +- **Goal:** Establish the current launch-readiness baseline and avoid working from stale branch assumptions. +- **Requirements:** R1, R2, R3; KTD3. +- **Files:** `docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md`, PR metadata, issue metadata, and any branch notes added to the PR body. +- **Approach:** Inspect current branch, uncommitted diff, open PRs, recent merged Swift branches, and open Swift/mobile/TestFlight issues. Decide whether to continue on `codex/swift-beta-tester-readiness`, switch to another branch, or create a new branch from the latest base. +- **Test scenarios:** + 1. Covers AE1. Existing uncommitted files are listed before any edits. + 2. Covers R1. Open Swift/mobile PRs and issues are summarized with scope classification. + 3. Covers R3. Version/build metadata is checked against Expo and TestFlight scripts. +- **Verification:** Branch state and issue scope are included in the final report and, when code changes ship, the PR description. + +### U2. Swift build, script, and release metadata verification + +- **Goal:** Prove the Swift app can build and its release metadata supports a seamless Expo replacement. +- **Requirements:** R3, R7, R10. +- **Files:** `apps/swift/project.yml`, `apps/swift/Resources/Info-iOS.plist`, `apps/swift/Resources/Info-macOS.plist`, `apps/swift/Resources/Info-watchOS.plist`, `apps/swift/scripts/lib/testflight-config.ts`, `apps/swift/scripts/verify-testflight-replacement.ts`, and related tests. +- **Approach:** Regenerate Swift config/project if needed, run Swift script tests, validate assets, inspect build settings, and run TestFlight replacement preflight/dry-run where credentials allow. +- **Test scenarios:** + 1. Covers AE3. Replacement preflight reports the intended bundle IDs, version, build number, and archive overrides. + 2. Swift script tests cover argument parsing, config generation, TestFlight config, asset validation, and screenshot runner logic. + 3. Xcode build settings expose no stale `1.0`/wrong bundle ID/default display name for the intended replacement path. +- **Verification:** `bun test:swift:scripts`, `bun swift:validate-assets`, `bun swift:testflight:preflight --replacement --production`, and Xcode build/show-settings checks pass or produce explicit blockers. + +### U3. iOS, iPad, macOS, and Watch screenshot catalog + +- **Goal:** Produce fresh visual evidence across supported Apple surfaces and inspect it for launch blockers. +- **Requirements:** R4, R5, R6; KTD1. +- **Files:** `apps/swift/scripts/capture-visual-screenshots.ts`, `apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift`, `apps/swift/Tests/PackRatMacUITests`, `apps/swift/scripts/watch-sync-smoke.ts`, and `artifacts/screenshots*`. +- **Approach:** Run screenshot capture for iOS, iPad, macOS, and Watch as supported. Save individual images and contact sheets. Review outputs manually and record visible defects by screen/state. +- **Test scenarios:** + 1. Covers AE2. Catalog includes Home, auth/guest, settings, packs/trips, catalog, weather, chat, templates, offline states, forms, modals, and menus. + 2. Contact sheets do not show cropped, stale, notification-obscured, or incorrectly connected states unless intentionally captured. + 3. Watch screenshot/sync smoke demonstrates companion behavior or records a tooling/signing limitation. +- **Verification:** Fresh contact sheets exist and the final report names their paths and visual findings. + +### U4. Full Swift E2E and core feature QA + +- **Goal:** Exercise launch-critical behavior beyond unit tests. +- **Requirements:** R7, R8, R9. +- **Files:** `apps/swift/Tests/PackRatUITests`, `apps/swift/Tests/PackRatMacUITests`, `apps/swift/Tests/PackRatTests`, `apps/swift/scripts/run-e2e.ts`, `apps/swift/scripts/run-e2e-macos.ts`, and deterministic API routes under `packages/api/src`. +- **Approach:** Run iOS smoke/full or targeted equivalent, iPad-capable screenshot/UITest paths, macOS smoke/full or targeted equivalent, Swift unit tests, and Watch sync smoke. Where full runs are too slow or flaky, identify the exact failing test and rerun targeted after fixes. +- **Test scenarios:** + 1. Authenticated and guest flows both reach expected screens without false connection-required states. + 2. Packs/trips/items CRUD and catalog add flows work with deterministic test data. + 3. Weather, season suggestions, trail conditions, and chat/AI flows show data/fallback states correctly. + 4. Settings/preferences and offline/local-first paths survive relaunch and network-disabled conditions. +- **Verification:** `bun test:swift:unit`, `bun e2e:swift:ios-smoke`, `bun e2e:swift:ios`, `bun e2e:swift:mac-smoke`, `bun e2e:swift:mac-ui`, and `bun swift:watch-sync-smoke` pass or residual blockers are filed. + +### U5. Fix launch-blocking defects with regression coverage + +- **Goal:** Convert QA findings into small, tested fixes without hiding real bugs. +- **Requirements:** R9, R11, R12; KTD4, KTD5. +- **Files:** Determined by findings, likely under `apps/swift/Sources/PackRat`, `apps/swift/Tests/PackRatTests`, `apps/swift/Tests/PackRatUITests`, `apps/swift/Tests/PackRatMacUITests`, and `packages/api/src/routes` for deterministic E2E stubs. +- **Approach:** For each defect, reproduce with a screenshot/test/log, apply the minimal SwiftUI-native fix, add or strengthen a regression assertion, and rerun the narrow gate plus the relevant broad gate. +- **Test scenarios:** + 1. A visible UI defect has before/after screenshot evidence. + 2. A functional defect has a failing or characterization test before the fix where practical. + 3. A residual limitation has a durable issue/PR note rather than being described as solved. +- **Verification:** Final diff contains only intentional fixes, tests pass, screenshots are refreshed after visual changes, and residuals are explicit. + +--- + +## Verification Contract + +| Gate | Applicability | Done signal | +|---|---|---| +| Branch hygiene | U1 | `git status --short --branch` is understood, with unrelated/user changes preserved | +| Swift script tests | U2 | `bun test:swift:scripts` passes | +| Asset and TestFlight metadata | U2 | `bun swift:validate-assets` and replacement preflight/dry-run pass or produce explicit credential blockers | +| Swift unit tests | U4, U5 | `bun test:swift:unit` passes | +| iOS E2E | U4, U5 | `bun e2e:swift:ios-smoke` and full or targeted iOS E2E pass | +| macOS E2E | U4, U5 | `bun e2e:swift:mac-smoke` and full or targeted macOS E2E pass | +| Screenshot catalog | U3, U5 | Fresh iOS, iPad, macOS, and Watch artifacts are generated where supported and manually reviewed | +| Watch sync smoke | U3, U4 | `bun swift:watch-sync-smoke` passes or a concrete watchOS tooling blocker is recorded | +| CI | All units | PR checks are green or non-Swift residuals are explicitly separated from Swift launch readiness | + +--- + +## Definition of Done + +- Latest branch, PR, and issue state has been checked and summarized. +- Existing uncommitted Swift tester fixes are preserved and either shipped or separated. +- Fresh screenshots/contact sheets exist for supported iPhone, iPad, macOS, and Watch states. +- Screenshots have been manually reviewed and any visible launch blockers are fixed or filed. +- Swift script, unit, iOS E2E, macOS E2E, and Watch smoke gates have passed or produced named blockers. +- TestFlight replacement metadata is consistent with the intended seamless Expo update path. +- Any code changes have focused regression coverage and do not weaken assertions to pass tests. +- PR/commit history clearly communicates what was fixed, what was validated, and what remains outside scope. diff --git a/packages/api/migrate.ts b/packages/api/migrate.ts index f2e364aa68..4353b7479d 100644 --- a/packages/api/migrate.ts +++ b/packages/api/migrate.ts @@ -1,13 +1,13 @@ +import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { setTimeout as sleep } from 'node:timers/promises'; import { fileURLToPath } from 'node:url'; import { neon, neonConfig } from '@neondatabase/serverless'; import { nodeEnv } from '@packrat/env/node'; +import { drizzle as drizzleBunSQL } from 'drizzle-orm/bun-sql'; +import { migrate as migrateBunSQL } from 'drizzle-orm/bun-sql/migrator'; import { drizzle } from 'drizzle-orm/neon-http'; import { migrate } from 'drizzle-orm/neon-http/migrator'; -import { drizzle as drizzlePg } from 'drizzle-orm/node-postgres'; -import { migrate as migratePg } from 'drizzle-orm/node-postgres/migrator'; -import { Client } from 'pg'; import WebSocket from 'ws'; // Required for Neon serverless driver to work in Node.js @@ -17,6 +17,7 @@ neonConfig.webSocketConstructor = WebSocket; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const STANDARD_POSTGRES_MIGRATION_ATTEMPTS = 3; +const STANDARD_POSTGRES_MIGRATION_SETTLE_TIMEOUT_MS = 120_000; // Check if we're using a standard PostgreSQL URL (for tests) vs Neon URL // Import the utility function from src/db/index.ts since it's defined there @@ -35,21 +36,69 @@ const isStandardPostgresUrl = (url: string) => { } }; +function expectedMigrationCount() { + const journal = JSON.parse( + readFileSync(join(__dirname, 'drizzle/meta/_journal.json'), 'utf8'), + ) as { entries?: unknown[] }; + return journal.entries?.length ?? 0; +} + +async function appliedMigrationCount(url: string) { + const sql = new Bun.SQL(url); + try { + const rows = await sql<{ count: number }[]>` + select count(*)::int as count + from drizzle.__drizzle_migrations + `; + return Number(rows[0]?.count ?? 0); + } catch { + return 0; + } finally { + await sql.close().catch(() => undefined); + } +} + +async function waitForPostgresMigrationLedger(input: { + url: string; + expectedCount: number; + timeoutMs: number; +}) { + const startedAt = Date.now(); + while (Date.now() - startedAt < input.timeoutMs) { + if ((await appliedMigrationCount(input.url)) >= input.expectedCount) return; + await sleep(1000); + } + throw new Error( + `PostgreSQL migration ledger did not reach ${input.expectedCount} entries within ${ + input.timeoutMs / 1000 + }s.`, + ); +} + async function runPostgresMigrations(url: string) { let lastError: unknown; + const expectedCount = expectedMigrationCount(); for (let attempt = 1; attempt <= STANDARD_POSTGRES_MIGRATION_ATTEMPTS; attempt += 1) { - const client = new Client({ connectionString: url }); + const sql = new Bun.SQL(url); try { - await client.connect(); - const db = drizzlePg(client); - await migratePg(db, { migrationsFolder: join(__dirname, 'drizzle') }); - await client.end(); + const db = drizzleBunSQL(sql); + const migration = migrateBunSQL(db, { migrationsFolder: join(__dirname, 'drizzle') }); + migration.catch(() => undefined); + await Promise.race([ + migration, + waitForPostgresMigrationLedger({ + url, + expectedCount, + timeoutMs: STANDARD_POSTGRES_MIGRATION_SETTLE_TIMEOUT_MS, + }), + ]); + await sql.close(); return; } catch (error) { lastError = error; - await client.end().catch(() => undefined); + await sql.close().catch(() => undefined); const message = error instanceof Error ? error.message : String(error); if (attempt === STANDARD_POSTGRES_MIGRATION_ATTEMPTS) { diff --git a/packages/api/scripts/e2e-local-start.ts b/packages/api/scripts/e2e-local-start.ts index 3e9646a4a9..8a992e1f54 100644 --- a/packages/api/scripts/e2e-local-start.ts +++ b/packages/api/scripts/e2e-local-start.ts @@ -183,7 +183,8 @@ const apiEnv = { nodeEnv.OPENAI_API_KEY ?? (envFileVars.OPENAI_API_KEY === 'sk-test' ? 'sk-e2e-stub-local' : envFileVars.OPENAI_API_KEY), WEATHER_API_KEY: nodeEnv.WEATHER_API_KEY ?? 'weather-e2e-stub-local', - APPLE_PRIVATE_KEY: nodeEnv.APPLE_PRIVATE_KEY ?? '', + APPLE_PRIVATE_KEY: + nodeEnv.APPLE_PRIVATE_KEY ?? envFileVars.APPLE_PRIVATE_KEY ?? 'e2e-apple-private-key', PACKRAT_PG_POOL_MAX: nodeEnv.PACKRAT_PG_POOL_MAX ?? envFileVars.PACKRAT_PG_POOL_MAX ?? '50', }; diff --git a/packages/api/scripts/e2e-node-server.ts b/packages/api/scripts/e2e-node-server.ts index 0516cf8927..a69cc27708 100644 --- a/packages/api/scripts/e2e-node-server.ts +++ b/packages/api/scripts/e2e-node-server.ts @@ -1,7 +1,7 @@ import { join } from 'node:path'; +import { preloadDbDrivers } from '@packrat/api/db'; import { nodeEnv } from '@packrat/env/node'; import { Miniflare } from 'miniflare'; -import worker from '../src/e2e-worker'; const port = Number(nodeEnv.PORT ?? 8787); @@ -10,6 +10,7 @@ const kvPersist = const noop = async () => {}; +console.log(`Preparing PackRat e2e API on port ${port}...`); const miniflare = new Miniflare({ script: 'export default { fetch() { return new Response("ok") } }', modules: true, @@ -17,7 +18,9 @@ const miniflare = new Miniflare({ kvPersist, logRequests: false, }); +console.log('Loading PackRat e2e AUTH_KV namespace...'); const authKv = await miniflare.getKVNamespace('AUTH_KV'); +console.log('PackRat e2e AUTH_KV namespace loaded.'); const bucket = { get: async () => null, @@ -54,6 +57,14 @@ const ctx = { passThroughOnException: () => {}, }; +console.log('Preloading PackRat e2e DB drivers...'); +await preloadDbDrivers(); +console.log('PackRat e2e DB drivers loaded.'); + +console.log('Loading PackRat e2e worker...'); +const { default: worker } = await import('../src/e2e-worker'); +console.log('PackRat e2e worker loaded.'); + Bun.serve({ port, hostname: '0.0.0.0', diff --git a/packages/api/src/auth/__tests__/mcp-token.test.ts b/packages/api/src/auth/__tests__/mcp-token.test.ts index ab11cb174d..d2c556761e 100644 --- a/packages/api/src/auth/__tests__/mcp-token.test.ts +++ b/packages/api/src/auth/__tests__/mcp-token.test.ts @@ -25,6 +25,7 @@ vi.mock('@packrat/db', () => ({ vi.mock('drizzle-orm', () => ({ eq: mocks.eq, + relations: vi.fn(() => ({})), })); vi.mock('jose', () => ({ diff --git a/packages/api/src/auth/__tests__/oauth-provider.test.ts b/packages/api/src/auth/__tests__/oauth-provider.test.ts index b6e0bffc37..8215bd23ed 100644 --- a/packages/api/src/auth/__tests__/oauth-provider.test.ts +++ b/packages/api/src/auth/__tests__/oauth-provider.test.ts @@ -14,7 +14,7 @@ * which the unit-test pool can't provide. */ -import { oauthAccessToken, oauthClient, oauthConsent, oauthRefreshToken } from '@packrat/db'; +import { oauthAccessToken, oauthClient, oauthConsent, oauthRefreshToken } from '@packrat/db/schema'; import { describe, expect, it } from 'vitest'; describe('OAuth provider schema (@packrat/db)', () => { diff --git a/packages/api/src/auth/auth.config.ts b/packages/api/src/auth/auth.config.ts index 961eade055..e1740c5b5c 100644 --- a/packages/api/src/auth/auth.config.ts +++ b/packages/api/src/auth/auth.config.ts @@ -13,7 +13,7 @@ import { drizzleAdapter } from '@better-auth/drizzle-adapter'; import { oauthProvider } from '@better-auth/oauth-provider'; import { neon } from '@neondatabase/serverless'; -import * as schema from '@packrat/db'; +import * as schema from '@packrat/db/schema'; import { type BetterAuthPlugin, betterAuth } from 'better-auth'; import { admin, bearer, jwt } from 'better-auth/plugins'; import { drizzle } from 'drizzle-orm/neon-http'; diff --git a/packages/api/src/auth/consent-route.ts b/packages/api/src/auth/consent-route.ts index 7c25ed0e0a..62a57309dd 100644 --- a/packages/api/src/auth/consent-route.ts +++ b/packages/api/src/auth/consent-route.ts @@ -26,7 +26,7 @@ import { getAuth } from '@packrat/api/auth'; import { createDb } from '@packrat/api/db'; import { getEnv } from '@packrat/api/utils/env-validation'; import { type OAuthClientRecord, renderConsentPage, renderSignInPage } from '@packrat/consent-ui'; -import * as dbSchema from '@packrat/db'; +import * as dbSchema from '@packrat/db/schema'; import { isString, toRecord, toString as toStr } from '@packrat/guards'; import { eq } from 'drizzle-orm'; import { Elysia } from 'elysia'; diff --git a/packages/api/src/auth/index.ts b/packages/api/src/auth/index.ts index 4242b46c8a..35dbbb2300 100644 --- a/packages/api/src/auth/index.ts +++ b/packages/api/src/auth/index.ts @@ -13,7 +13,7 @@ import { oauthProvider } from '@better-auth/oauth-provider'; import { generateAppleClientSecret, verifyPasswordCompat } from '@packrat/api/auth/auth.helpers'; import { createConnection } from '@packrat/api/db'; import type { ValidatedEnv } from '@packrat/api/utils/env-validation'; -import * as schema from '@packrat/db'; +import * as schema from '@packrat/db/schema'; import { isObject } from '@packrat/guards'; import { safeJsonParse } from '@packrat/utils'; import { type BetterAuthPlugin, betterAuth } from 'better-auth'; diff --git a/packages/api/src/auth/local-e2e.ts b/packages/api/src/auth/local-e2e.ts index a3afb24599..0446a30f96 100644 --- a/packages/api/src/auth/local-e2e.ts +++ b/packages/api/src/auth/local-e2e.ts @@ -19,6 +19,7 @@ export type LocalE2EUser = { export function isLocalE2EAuthEnabled(env: ValidatedEnv): boolean { const dbUrl = env.NEON_DATABASE_URL; return ( + typeof dbUrl === 'string' && (dbUrl.includes('127.0.0.1') || dbUrl.includes('localhost')) && Boolean(env.E2E_TEST_EMAIL) && Boolean(env.E2E_TEST_PASSWORD) && diff --git a/packages/api/src/auth/mcp-token.ts b/packages/api/src/auth/mcp-token.ts index 1b4af925c6..2ff745553f 100644 --- a/packages/api/src/auth/mcp-token.ts +++ b/packages/api/src/auth/mcp-token.ts @@ -1,7 +1,7 @@ import { createDb } from '@packrat/api/db'; import type { AuthUser } from '@packrat/api/middleware/auth'; import type { ValidatedEnv } from '@packrat/api/utils/env-validation'; -import { users } from '@packrat/db'; +import { users } from '@packrat/db/schema'; import { isString } from '@packrat/guards'; import { eq } from 'drizzle-orm'; import { createRemoteJWKSet, jwtVerify } from 'jose'; diff --git a/packages/api/src/db/index.ts b/packages/api/src/db/index.ts index ff0aac91b8..e07ff8b9a0 100644 --- a/packages/api/src/db/index.ts +++ b/packages/api/src/db/index.ts @@ -12,8 +12,6 @@ import * as schema from '@packrat/db/schema'; import { isFunction, isString } from '@packrat/guards'; import { drizzle } from 'drizzle-orm/neon-http'; import { drizzle as drizzleServerless } from 'drizzle-orm/neon-serverless'; -import { drizzle as drizzlePg } from 'drizzle-orm/node-postgres'; -import { Pool } from 'pg'; const runtimeEnv = () => (globalThis as { process?: { env?: Record } }).process?.env ?? {}; @@ -41,11 +39,23 @@ const isStandardPostgresUrl = (url: string) => { } }; -const pgPools = new Map(); +const bunSqlClients = new Map(); +type DrizzleBunSql = typeof import('drizzle-orm/bun-sql')['drizzle']; +let cachedDrizzleBunSQL: DrizzleBunSql | undefined; -const getPgPoolMax = () => { - const parsed = Number(runtimeEnv().PACKRAT_PG_POOL_MAX); - return Number.isFinite(parsed) && parsed > 0 ? parsed : 5; +export const preloadDbDrivers = async () => { + if (!cachedDrizzleBunSQL && 'Bun' in globalThis) { + cachedDrizzleBunSQL = (await import('drizzle-orm/bun-sql')).drizzle; + } +}; + +const loadDrizzleBunSQL = (): DrizzleBunSql => { + if (!cachedDrizzleBunSQL) { + throw new Error( + 'Bun SQL driver was not preloaded before creating a local Postgres connection.', + ); + } + return cachedDrizzleBunSQL; }; const shouldUseNeonWsProxy = (url: string) => { @@ -145,24 +155,16 @@ export const createConnection = ({ url, useNeonHttp }: { url: string; useNeonHtt return withTagging(drizzleServerless(neonPool, { schema })); } - let pool = pgPools.get(url); - if (!pool) { - const newPool = new Pool({ - connectionString: url, - max: getPgPoolMax(), - // idleTimeoutMillis: 0 prevents pg.Pool from calling setTimeout().unref(), - // which is not supported in the Cloudflare Workers runtime (miniflare). - idleTimeoutMillis: 0, - connectionTimeoutMillis: 10000, - }); - newPool.on('error', () => { - pgPools.delete(url); - }); - pgPools.set(url, newPool); - pool = newPool; + if (!('Bun' in globalThis) || !Bun.SQL) { + throw new Error('Standard PostgreSQL URLs require the Bun SQL runtime.'); + } + + let sql = bunSqlClients.get(url); + if (!sql) { + sql = new Bun.SQL(url); + bunSqlClients.set(url, sql); } - instrumentPool(pool); - return withTagging(drizzlePg(pool, { schema })); + return withTagging(loadDrizzleBunSQL()(sql, { schema })); } if (useNeonHttp) { const fn = instrumentNeonFn(neon(url)); diff --git a/packages/api/src/db/seed-e2e-user.ts b/packages/api/src/db/seed-e2e-user.ts index 4b288b35ba..b94fcdc8ec 100644 --- a/packages/api/src/db/seed-e2e-user.ts +++ b/packages/api/src/db/seed-e2e-user.ts @@ -6,22 +6,17 @@ * bun run packages/api/src/db/seed-e2e-user.ts * * Re-running is safe: if the user exists, the password hash and - * `emailVerified=true` flag are refreshed via `db.update` (drizzle-seed - * has no UPDATE primitive); otherwise the user is created via the - * `drizzle-seed` `.refine()` API so this seeder shares the same tool - * surface as the other prod-config seeders. Every column is fixed via - * `f.default()` because drizzle-seed generates a random value for any - * column not listed in `.refine()`. + * `emailVerified=true` flag are refreshed via `db.update`; otherwise the + * user is created via a direct insert so this script avoids package-export + * differences in local E2E runtimes. */ import { neon, neonConfig } from '@neondatabase/serverless'; import * as schema from '@packrat/db/schema'; import { nodeEnv } from '@packrat/env/node'; import { eq } from 'drizzle-orm'; +import { type BunSQLDatabase, drizzle as drizzleBunSQL } from 'drizzle-orm/bun-sql'; import { drizzle, type NeonHttpDatabase } from 'drizzle-orm/neon-http'; -import { drizzle as drizzlePg, type NodePgDatabase } from 'drizzle-orm/node-postgres'; -import { seed } from 'drizzle-seed'; -import { Client } from 'pg'; import WebSocket from 'ws'; import { hashPassword } from '../utils/auth'; @@ -50,14 +45,13 @@ async function seedE2EUser(): Promise { const normalizedEmail = email.toLowerCase(); - type SeedDatabase = NodePgDatabase | NeonHttpDatabase; + type SeedDatabase = BunSQLDatabase | NeonHttpDatabase; let db: SeedDatabase; - let pgClient: Client | undefined; + let sql: Bun.SQL | undefined; if (isStandardPostgresUrl(dbUrl)) { - pgClient = new Client({ connectionString: dbUrl }); - await pgClient.connect(); - db = drizzlePg(pgClient, { schema }); + sql = new Bun.SQL(dbUrl); + db = drizzleBunSQL(sql, { schema }); } else { db = drizzle(neon(dbUrl), { schema }); } @@ -84,28 +78,19 @@ async function seedE2EUser(): Promise { } else { userId = crypto.randomUUID(); const now = new Date(); - await seed(db, { users: schema.users }).refine((f) => ({ - users: { - count: 1, - columns: { - id: f.default({ defaultValue: userId }), - name: f.default({ defaultValue: 'E2E Automation' }), - email: f.default({ defaultValue: normalizedEmail }), - emailVerified: f.default({ defaultValue: true }), - image: f.default({ defaultValue: null }), - role: f.default({ defaultValue: 'USER' }), - banned: f.default({ defaultValue: false }), - banReason: f.default({ defaultValue: null }), - banExpires: f.default({ defaultValue: null }), - firstName: f.default({ defaultValue: 'E2E' }), - lastName: f.default({ defaultValue: 'Automation' }), - avatarUrl: f.default({ defaultValue: null }), - passwordHash: f.default({ defaultValue: passwordHash }), - createdAt: f.default({ defaultValue: now }), - updatedAt: f.default({ defaultValue: now }), - }, - }, - })); + await db.insert(schema.users).values({ + id: userId, + name: 'E2E Automation', + email: normalizedEmail, + emailVerified: true, + role: 'USER', + banned: false, + firstName: 'E2E', + lastName: 'Automation', + passwordHash, + createdAt: now, + updatedAt: now, + }); console.log(`E2E user created: ${normalizedEmail} (id=${userId})`); } @@ -113,8 +98,6 @@ async function seedE2EUser(): Promise { // Upsert the credential account row that better-auth looks up during sign-in. // better-auth sets accountId = user.id for the 'credential' provider. - // (drizzle-seed has no upsert; this requires onConflictDoUpdate so we use - // db.insert directly here rather than drizzle-seed's refine path.) await db .insert(schema.account) .values({ @@ -189,7 +172,7 @@ async function seedE2EUser(): Promise { .onConflictDoNothing({ target: schema.catalogItems.sku }); console.log('E2E catalog fixtures ensured'); } finally { - await pgClient?.end(); + await sql?.close(); } } diff --git a/packages/api/src/e2e-worker.ts b/packages/api/src/e2e-worker.ts index dde861c742..8ad1a8ff8d 100644 --- a/packages/api/src/e2e-worker.ts +++ b/packages/api/src/e2e-worker.ts @@ -1,15 +1,28 @@ -import { - oauthProviderAuthServerMetadata, - oauthProviderOpenIdConfigMetadata, -} from '@better-auth/oauth-provider'; import type { MessageBatch } from '@cloudflare/workers-types'; import { addCorsHeaders, appBase, corsPreflightResponse } from '@packrat/api/app'; -import { getAuth } from '@packrat/api/auth'; +import { + isLocalE2EAuthEnabled, + localE2EToken, + makeLocalE2EUser, +} from '@packrat/api/auth/local-e2e'; import { type Env, getEnv, setWorkerEnv } from '@packrat/api/utils/env-validation'; const WELL_KNOWN_AUTH_SERVER_PATH = '/.well-known/oauth-authorization-server'; const WELL_KNOWN_OPENID_CONFIG_PATH = '/.well-known/openid-configuration'; const WELL_KNOWN_AUTH_BASE_PATH = '/api/auth'; +const bearerPrefixRegex = /^Bearer\s+/i; + +async function loadAuth() { + const { getAuth } = await import('@packrat/api/auth'); + return getAuth; +} + +async function loadOAuthMetadataHandlers() { + const { oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata } = await import( + '@better-auth/oauth-provider' + ); + return { oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata }; +} function enrichEnv(env: Env): Env { if (env.OSM_HYPERDRIVE) { @@ -38,6 +51,41 @@ function wellKnownMetadataKind(pathname: string): 'openid' | 'authorization-serv return null; } +async function handleLocalE2EAuth(request: Request, env: Env): Promise { + if (!isLocalE2EAuthEnabled(env)) return undefined; + + const url = new URL(request.url); + if (request.method === 'POST' && url.pathname === '/api/auth/sign-in/email') { + const body = (await request.json().catch(() => undefined)) as + | { email?: string; password?: string } + | undefined; + const email = body?.email?.toLowerCase(); + if (email !== env.E2E_TEST_EMAIL?.toLowerCase() || body?.password !== env.E2E_TEST_PASSWORD) { + return Response.json({ error: 'Invalid email or password' }, { status: 401 }); + } + + const token = await localE2EToken(env); + return Response.json( + { + redirect: false, + token, + user: makeLocalE2EUser(env), + }, + { headers: { 'set-auth-token': token } }, + ); + } + + if (request.method === 'POST' && url.pathname === '/api/auth/sign-out') { + const expected = await localE2EToken(env); + const authorization = request.headers.get('Authorization') ?? ''; + if (authorization.replace(bearerPrefixRegex, '') === expected) { + return Response.json({ success: true }); + } + } + + return undefined; +} + export default { // biome-ignore lint/complexity/useMaxParams: Cloudflare Worker fetch callbacks receive request, env, and context. async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { @@ -48,7 +96,10 @@ export default { if (request.method === 'GET') { const metadataKind = wellKnownMetadataKind(url.pathname); if (metadataKind) { + const getAuth = await loadAuth(); const auth = await getAuth(e); + const { oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata } = + await loadOAuthMetadataHandlers(); const handler = metadataKind === 'openid' ? oauthProviderOpenIdConfigMetadata(auth) @@ -61,6 +112,10 @@ export default { const preflight = corsPreflightResponse(request); if (preflight) return preflight; + const localAuthResponse = await handleLocalE2EAuth(request, e); + if (localAuthResponse) return addCorsHeaders({ request, response: localAuthResponse }); + + const getAuth = await loadAuth(); const auth = await getAuth(e); return addCorsHeaders({ request, response: await auth.handler(request) }); } diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 98015df4f6..1f63519b7d 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -36,7 +36,7 @@ import { import { captureApiException, record } from '@packrat/api/utils/sentry'; import { CatalogEtlWorkflow as RawCatalogEtlWorkflow } from '@packrat/api/workflows/catalog-etl-workflow'; import { renderConsentPage, renderSignInPage } from '@packrat/consent-ui'; -import * as dbSchema from '@packrat/db'; +import * as dbSchema from '@packrat/db/schema'; import { isString, toRecord, toString as toStr } from '@packrat/guards'; import { safeJsonParse, safeJsonStringify } from '@packrat/utils'; import { instrumentWorkflowWithSentry, withSentry } from '@sentry/cloudflare'; diff --git a/packages/api/src/middleware/auth.ts b/packages/api/src/middleware/auth.ts index 399a460fd8..3bdbf6ee69 100644 --- a/packages/api/src/middleware/auth.ts +++ b/packages/api/src/middleware/auth.ts @@ -1,4 +1,3 @@ -import { getAuth } from '@packrat/api/auth'; import { getLocalE2EUserFromRequest } from '@packrat/api/auth/local-e2e'; import { resolveMcpBearerUser } from '@packrat/api/auth/mcp-token'; import { isValidApiKey } from '@packrat/api/utils/auth'; @@ -15,6 +14,11 @@ export type AuthUser = { name: string; }; +async function loadAuth() { + const { getAuth } = await import('@packrat/api/auth'); + return getAuth; +} + /** * Elysia macro that enforces Better Auth session authentication. * @@ -37,6 +41,7 @@ export const authPlugin = new Elysia({ name: 'packrat-auth' }).macro({ return { user }; } + const getAuth = await loadAuth(); const auth = await getAuth(env); let session: Awaited>; @@ -95,6 +100,7 @@ export const adminAuthPlugin = new Elysia({ name: 'packrat-admin-auth' }).macro( const localUser = await getLocalE2EUserFromRequest(env, request); if (localUser) return status(403, { error: 'Forbidden' }); + const getAuth = await loadAuth(); const auth = await getAuth(env); let session: Awaited>; diff --git a/packages/api/src/routes/admin/analytics/catalog.ts b/packages/api/src/routes/admin/analytics/catalog.ts index ec5d241618..c4eeea1274 100644 --- a/packages/api/src/routes/admin/analytics/catalog.ts +++ b/packages/api/src/routes/admin/analytics/catalog.ts @@ -4,7 +4,7 @@ import { getEnv } from '@packrat/api/utils/env-validation'; import { captureApiException } from '@packrat/api/utils/sentry'; import type { CatalogEtlWorkflowParams } from '@packrat/api/workflows/catalog-etl-workflow'; import { type ChunkSpec, chunkCsvForR2 } from '@packrat/api/workflows/shared/chunkCsvForR2'; -import { catalogItems, etlJobs, invalidItemLogs } from '@packrat/db'; +import { catalogItems, etlJobs, invalidItemLogs } from '@packrat/db/schema'; import { AdminErrorResponses, BrandRowSchema, diff --git a/packages/api/src/routes/admin/analytics/platform.ts b/packages/api/src/routes/admin/analytics/platform.ts index 142d8522a9..bdfcd0bfbd 100644 --- a/packages/api/src/routes/admin/analytics/platform.ts +++ b/packages/api/src/routes/admin/analytics/platform.ts @@ -1,5 +1,12 @@ import { createDb } from '@packrat/api/db'; -import { catalogItems, packs, posts, trailConditionReports, trips, users } from '@packrat/db'; +import { + catalogItems, + packs, + posts, + trailConditionReports, + trips, + users, +} from '@packrat/db/schema'; import { ActiveUsersSchema, ActivityPointSchema, diff --git a/packages/api/src/routes/admin/index.ts b/packages/api/src/routes/admin/index.ts index 67308c6194..533013d692 100644 --- a/packages/api/src/routes/admin/index.ts +++ b/packages/api/src/routes/admin/index.ts @@ -1,12 +1,11 @@ import { cors } from '@elysiajs/cors'; -import { getAuth } from '@packrat/api/auth'; import { resolveMcpBearerUser } from '@packrat/api/auth/mcp-token'; import { createDb } from '@packrat/api/db'; import { verifyCFAccessRequest } from '@packrat/api/middleware/cfAccess'; import { timingSafeEqual } from '@packrat/api/utils/auth'; import { getEnv } from '@packrat/api/utils/env-validation'; import { captureApiException } from '@packrat/api/utils/sentry'; -import { catalogItems, packs, users } from '@packrat/db'; +import { catalogItems, packs, users } from '@packrat/db/schema'; import { assertAllDefined, queryBoolean } from '@packrat/guards'; import { AdminCatalogListSchema, @@ -39,6 +38,11 @@ const ADMIN_TOKEN_TTL_SECONDS = 3600; // 1 hour const ADMIN_JWT_ISSUER = 'packrat-api'; const ADMIN_JWT_AUDIENCE = 'packrat-admin'; +async function loadAuth() { + const { getAuth } = await import('@packrat/api/auth'); + return getAuth; +} + function checkAdminCredentials({ username, password, @@ -115,6 +119,7 @@ async function verifyBetterAuthAdmin(request: Request): Promise { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), BETTER_AUTH_GUARD_TIMEOUT_MS); try { + const getAuth = await loadAuth(); const auth = await getAuth(env); // Run the session lookup in a Promise.race against the timeout so a // slow/hanging Neon-backed Better Auth doesn't block the guard. diff --git a/packages/api/src/routes/admin/trails.ts b/packages/api/src/routes/admin/trails.ts index 0a6bd1fa3d..bf059b9b7d 100644 --- a/packages/api/src/routes/admin/trails.ts +++ b/packages/api/src/routes/admin/trails.ts @@ -1,5 +1,5 @@ import { createDb, createOsmDb } from '@packrat/api/db'; -import { trailConditionReports, users } from '@packrat/db'; +import { trailConditionReports, users } from '@packrat/db/schema'; import { queryBoolean } from '@packrat/guards'; import { AdminErrorResponses, diff --git a/packages/api/src/routes/catalog/index.ts b/packages/api/src/routes/catalog/index.ts index 861d3834f6..3e4a150334 100644 --- a/packages/api/src/routes/catalog/index.ts +++ b/packages/api/src/routes/catalog/index.ts @@ -10,7 +10,7 @@ import { getEnv, isLocalE2EApiEnv } from '@packrat/api/utils/env-validation'; import { captureApiException } from '@packrat/api/utils/sentry'; import type { CatalogEtlWorkflowParams } from '@packrat/api/workflows/catalog-etl-workflow'; import { type ChunkSpec, chunkCsvForR2 } from '@packrat/api/workflows/shared/chunkCsvForR2'; -import { catalogItems, etlJobs, packItems } from '@packrat/db'; +import { catalogItems, etlJobs, packItems } from '@packrat/db/schema'; import { isNumber, isObject, isString } from '@packrat/guards'; import { CatalogCategoriesResponseSchema, diff --git a/packages/api/src/routes/chat.ts b/packages/api/src/routes/chat.ts index 329167fd67..1744acd63b 100644 --- a/packages/api/src/routes/chat.ts +++ b/packages/api/src/routes/chat.ts @@ -4,20 +4,13 @@ import { createAIProvider } from '@packrat/api/utils/ai/provider'; import { createTools } from '@packrat/api/utils/ai/tools'; import { getEnv } from '@packrat/api/utils/env-validation'; import { apiAddBreadcrumb, captureApiException } from '@packrat/api/utils/sentry'; -import { reportedContent } from '@packrat/db'; +import { reportedContent } from '@packrat/db/schema'; import { ChatRequestSchema, CreateReportRequestSchema, UpdateReportStatusRequestSchema, } from '@packrat/schemas/chat'; -import { - convertToModelMessages, - createUIMessageStream, - createUIMessageStreamResponse, - stepCountIs, - streamText, - type UIMessage, -} from 'ai'; +import type { UIMessage } from 'ai'; import { eq } from 'drizzle-orm'; import { Elysia, status } from 'elysia'; import { z } from 'zod'; @@ -27,6 +20,39 @@ import { getSchemaInfo } from '../utils/DbUtils'; const isE2EStubOpenAiKey = (openAiApiKey: string | undefined) => openAiApiKey === 'sk-test' || openAiApiKey?.startsWith('sk-e2e-stub-') === true; +function e2eChatStreamResponse() { + const text = + 'For this e2e pack, three essential items are a shelter, a sleep system, and water treatment.'; + const stream = new ReadableStream({ + start(controller) { + const encoder = new TextEncoder(); + controller.enqueue( + encoder.encode( + `data: ${JSON.stringify({ type: 'text-start', id: 'e2e-chat-response' })}\n\n`, + ), + ); + controller.enqueue( + encoder.encode( + `data: ${JSON.stringify({ type: 'text-delta', id: 'e2e-chat-response', delta: text })}\n\n`, + ), + ); + controller.enqueue( + encoder.encode( + `data: ${JSON.stringify({ type: 'text-end', id: 'e2e-chat-response' })}\n\n`, + ), + ); + controller.enqueue(encoder.encode('data: [DONE]\n\n')); + controller.close(); + }, + }); + return new Response(stream, { + headers: { + 'Content-Type': 'text/event-stream; charset=utf-8', + 'Cache-Control': 'no-cache', + }, + }); +} + export const chatRoutes = new Elysia({ prefix: '/chat' }) .model({ 'chat.ChatRequest': ChatRequestSchema, @@ -63,7 +89,6 @@ export const chatRoutes = new Elysia({ prefix: '/chat' }) speedUnit, } = typedBody; - const tools = createTools(user.userId); const schemaInfo = await getSchemaInfo(); let systemPrompt = ` @@ -118,25 +143,10 @@ export const chatRoutes = new Elysia({ prefix: '/chat' }) } if (isE2EStubOpenAiKey(OPENAI_API_KEY)) { - return createUIMessageStreamResponse({ - stream: createUIMessageStream({ - originalMessages: messages, - execute: ({ writer }) => { - const id = 'e2e-chat-response'; - writer.write({ type: 'text-start', id }); - writer.write({ - type: 'text-delta', - id, - delta: - 'For this e2e pack, three essential items are a shelter, a sleep system, and water treatment.', - }); - writer.write({ type: 'text-end', id }); - writer.write({ type: 'finish', finishReason: 'stop' }); - }, - }), - }); + return e2eChatStreamResponse(); } + const tools = await createTools(user.userId); const aiProvider = createAIProvider({ openAiApiKey: OPENAI_API_KEY, provider: AI_PROVIDER, @@ -170,6 +180,7 @@ export const chatRoutes = new Elysia({ prefix: '/chat' }) }, }); + const { convertToModelMessages, stepCountIs, streamText } = await import('ai'); const result = streamText({ model: aiProvider(DEFAULT_MODELS.OPENAI_CHAT), system: systemPrompt, diff --git a/packages/api/src/routes/feed/index.ts b/packages/api/src/routes/feed/index.ts index 0b184e2a12..e05f2762e8 100644 --- a/packages/api/src/routes/feed/index.ts +++ b/packages/api/src/routes/feed/index.ts @@ -1,6 +1,6 @@ import { createDb } from '@packrat/api/db'; import { authPlugin } from '@packrat/api/middleware/auth'; -import { commentLikes, postComments, postLikes, posts, users } from '@packrat/db'; +import { commentLikes, postComments, postLikes, posts, users } from '@packrat/db/schema'; import { CreateCommentRequestSchema, CreatePostRequestSchema, diff --git a/packages/api/src/routes/guides/index.ts b/packages/api/src/routes/guides/index.ts index cfc4383757..98ddbd3e93 100644 --- a/packages/api/src/routes/guides/index.ts +++ b/packages/api/src/routes/guides/index.ts @@ -1,5 +1,5 @@ import { authPlugin } from '@packrat/api/middleware/auth'; -import { R2BucketService } from '@packrat/api/services/r2-bucket'; +import type { R2BucketService } from '@packrat/api/services/r2-bucket'; import { getEnv, isLocalE2EApiEnv } from '@packrat/api/utils/env-validation'; import { captureApiException } from '@packrat/api/utils/sentry'; import { asNumber, asString, isArray } from '@packrat/guards'; @@ -12,11 +12,23 @@ import { GuidesResponseSchema, } from '@packrat/schemas/guides'; import { Elysia, NotFoundError, status } from 'elysia'; -import matter from 'gray-matter'; import { z } from 'zod'; const MDX_EXT_RE = /\.(mdx?|md)$/; const DASH_RE = /-/g; +async function parseGuideMatter(content: string) { + const { default: matter } = await import('gray-matter'); + return matter(content); +} + +async function createGuidesBucket(): Promise { + const { R2BucketService } = await import('@packrat/api/services/r2-bucket'); + return new R2BucketService({ + env: getEnv(), + bucketType: 'guides', + }); +} + const isLocalE2EGuidesEnv = () => { const { E2E_TEST_USER_ID, NEON_DATABASE_URL } = getEnv(); return isLocalE2EApiEnv({ @@ -126,10 +138,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) return GuidesResponseSchema.parse(paginateGuides({ items: guides, page, limit })); } - const bucket = new R2BucketService({ - env: getEnv(), - bucketType: 'guides', - }); + const bucket = await createGuidesBucket(); const list = await bucket.list(); @@ -140,7 +149,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) const response = await bucket.get(obj.key); if (response) { const text = await response.text(); - const { data } = matter(text); + const { data } = await parseGuideMatter(text); frontmatter = data; } } catch (error) { @@ -232,10 +241,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) return GuideCategoriesResponseSchema.parse({ categories, count: categories.length }); } - const bucket = new R2BucketService({ - env: getEnv(), - bucketType: 'guides', - }); + const bucket = await createGuidesBucket(); const list = await bucket.list(); const categoriesSet = new Set(); @@ -245,7 +251,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) const response = await bucket.get(obj.key); if (response) { const text = await response.text(); - const { data } = matter(text); + const { data } = await parseGuideMatter(text); if (data.categories && Array.isArray(data.categories)) { return data.categories as string[]; } @@ -317,10 +323,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) }); } - const bucket = new R2BucketService({ - env: getEnv(), - bucketType: 'guides', - }); + const bucket = await createGuidesBucket(); const list = await bucket.list({ limit: 1000 }); const searchResults = await Promise.all( @@ -419,10 +422,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) return GuideDetailSchema.parse(guide); } - const bucket = new R2BucketService({ - env: getEnv(), - bucketType: 'guides', - }); + const bucket = await createGuidesBucket(); let key = `${id}.mdx`; let object = await bucket.get(key); @@ -439,7 +439,7 @@ export const guidesRoutes = new Elysia({ prefix: '/guides' }) const headResult = await bucket.head(key); const metadata = headResult?.customMetadata || {}; const rawContent = await object.text(); - const { data: frontmatter, content } = matter(rawContent); + const { data: frontmatter, content } = await parseGuideMatter(rawContent); return GuideDetailSchema.parse({ id, diff --git a/packages/api/src/routes/knowledgeBase/reader.ts b/packages/api/src/routes/knowledgeBase/reader.ts index 00f8ab5b55..2976e522f0 100644 --- a/packages/api/src/routes/knowledgeBase/reader.ts +++ b/packages/api/src/routes/knowledgeBase/reader.ts @@ -1,6 +1,4 @@ -import { Readability } from '@mozilla/readability'; import { Elysia, status } from 'elysia'; -import { parseHTML } from 'linkedom'; import { z } from 'zod'; // \u2500\u2500 HTML \u2192 Markdown conversion patterns \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 @@ -92,6 +90,10 @@ export const readerRoutes = new Elysia({ prefix: '/reader' }).post( const html = await response.text(); + const [{ Readability }, { parseHTML }] = await Promise.all([ + import('@mozilla/readability'), + import('linkedom'), + ]); const { window } = parseHTML(html); const reader = new Readability(window.document); const article = reader.parse(); diff --git a/packages/api/src/routes/packTemplates/index.ts b/packages/api/src/routes/packTemplates/index.ts index 44b7639956..5517a78f01 100644 --- a/packages/api/src/routes/packTemplates/index.ts +++ b/packages/api/src/routes/packTemplates/index.ts @@ -4,7 +4,7 @@ import { CatalogService } from '@packrat/api/services/catalogService'; import { createGoogleAIProvider } from '@packrat/api/utils/ai/provider'; import { getEnv } from '@packrat/api/utils/env-validation'; import { setQueryTag } from '@packrat/api/utils/queryMetrics'; -import { type PackTemplate, packTemplateItems, packTemplates } from '@packrat/db'; +import { type PackTemplate, packTemplateItems, packTemplates } from '@packrat/db/schema'; import { assertDefined } from '@packrat/guards'; import { AIPackAnalysisSchema, @@ -15,7 +15,6 @@ import { UpdatePackTemplateRequestSchema, } from '@packrat/schemas/packTemplates'; import { safeJsonStringify } from '@packrat/utils'; -import { generateObject } from 'ai'; import { and, eq, or, sql } from 'drizzle-orm'; import { Elysia, status } from 'elysia'; import { fetchTranscript } from 'youtube-transcript'; @@ -314,6 +313,7 @@ export const packTemplatesRoutes = new Elysia({ prefix: '/pack-templates' }) throw new Error('No content found in TikTok post (no images or video)'); } + const { generateObject } = await import('ai'); const { object: analysis } = await generateObject({ model: google('gemini-3-flash-preview'), schema: AIPackAnalysisSchema, diff --git a/packages/api/src/routes/packs/index.ts b/packages/api/src/routes/packs/index.ts index 72ba348c11..2ca2d68c5a 100644 --- a/packages/api/src/routes/packs/index.ts +++ b/packages/api/src/routes/packs/index.ts @@ -1,4 +1,3 @@ -import { GetObjectCommand } from '@aws-sdk/client-s3'; import { createDb } from '@packrat/api/db'; import { adminAuthPlugin, authPlugin } from '@packrat/api/middleware/auth'; import { ImageDetectionService, PackService } from '@packrat/api/services'; @@ -21,7 +20,7 @@ import { packItems, packs, packWeightHistory, -} from '@packrat/db'; +} from '@packrat/db/schema'; import { AnalyzeImageRequestSchema } from '@packrat/schemas/imageDetection'; import { AddPackItemBodySchema, @@ -258,6 +257,7 @@ export const packsRoutes = new Elysia({ prefix: '/packs' }) } const { PACKRAT_BUCKET_R2_BUCKET_NAME, PACKRAT_BUCKET } = getEnv(); + const { GetObjectCommand } = await import('@aws-sdk/client-s3'); const command = new GetObjectCommand({ Bucket: PACKRAT_BUCKET_R2_BUCKET_NAME, Key: image, diff --git a/packages/api/src/routes/seasonSuggestions.ts b/packages/api/src/routes/seasonSuggestions.ts index 201f583a9e..ec9216a2cd 100644 --- a/packages/api/src/routes/seasonSuggestions.ts +++ b/packages/api/src/routes/seasonSuggestions.ts @@ -2,9 +2,8 @@ import { createDb } from '@packrat/api/db'; import { authPlugin } from '@packrat/api/middleware/auth'; import { createAIProvider } from '@packrat/api/utils/ai/provider'; import { getEnv, isLocalE2EApiEnv } from '@packrat/api/utils/env-validation'; -import { type PackItem, packItems } from '@packrat/db'; +import { type PackItem, packItems } from '@packrat/db/schema'; import { SeasonSuggestionsRequestSchema } from '@packrat/schemas/seasonSuggestions'; -import { generateObject } from 'ai'; import { and, eq } from 'drizzle-orm'; import { Elysia, status } from 'elysia'; import { z } from 'zod'; @@ -150,6 +149,7 @@ ${inventoryFormatted}`; cloudflareApiToken: CLOUDFLARE_API_TOKEN, cloudflareAiBinding: AI, }); + const { generateObject } = await import('ai'); const { object } = await generateObject({ model: aiProvider(DEFAULT_MODELS.OPENAI_CHAT), schema: z.object({ diff --git a/packages/api/src/routes/trailConditions/reports.ts b/packages/api/src/routes/trailConditions/reports.ts index 9873433dc0..b9b4a00eb0 100644 --- a/packages/api/src/routes/trailConditions/reports.ts +++ b/packages/api/src/routes/trailConditions/reports.ts @@ -1,8 +1,8 @@ import { createDb } from '@packrat/api/db'; import { authPlugin } from '@packrat/api/middleware/auth'; import { captureApiException } from '@packrat/api/utils/sentry'; -import type { NewTrailConditionReport } from '@packrat/db'; -import { trailConditionReports } from '@packrat/db'; +import type { NewTrailConditionReport } from '@packrat/db/schema'; +import { trailConditionReports } from '@packrat/db/schema'; import { CreateTrailConditionReportRequestSchema, UpdateTrailConditionReportRequestSchema, diff --git a/packages/api/src/routes/trips/index.ts b/packages/api/src/routes/trips/index.ts index 455d63a359..44f172a1d9 100644 --- a/packages/api/src/routes/trips/index.ts +++ b/packages/api/src/routes/trips/index.ts @@ -1,6 +1,6 @@ import { createDb } from '@packrat/api/db'; import { authPlugin } from '@packrat/api/middleware/auth'; -import { trips } from '@packrat/db'; +import { trips } from '@packrat/db/schema'; import { CreateTripBodySchema, TripSchema, UpdateTripBodySchema } from '@packrat/schemas/trips'; import { and, eq } from 'drizzle-orm'; import { Elysia, NotFoundError, status } from 'elysia'; diff --git a/packages/api/src/routes/user/index.ts b/packages/api/src/routes/user/index.ts index 422071d000..895bc9d1b6 100644 --- a/packages/api/src/routes/user/index.ts +++ b/packages/api/src/routes/user/index.ts @@ -1,7 +1,7 @@ import { createDb } from '@packrat/api/db'; import { authPlugin } from '@packrat/api/middleware/auth'; import { captureApiException } from '@packrat/api/utils/sentry'; -import { users } from '@packrat/db'; +import { users } from '@packrat/db/schema'; import { ErrorResponseSchema } from '@packrat/schemas/shared'; import { GetPreferencesResponseSchema, diff --git a/packages/api/src/services/__tests__/entitlementsService.test.ts b/packages/api/src/services/__tests__/entitlementsService.test.ts index 356c147461..2d581ab97b 100644 --- a/packages/api/src/services/__tests__/entitlementsService.test.ts +++ b/packages/api/src/services/__tests__/entitlementsService.test.ts @@ -31,6 +31,7 @@ vi.mock('drizzle-orm', () => ({ gt: (col: unknown, val: unknown) => ({ gt: [col, val] }), isNull: (col: unknown) => ({ isNull: col }), or: (...conds: unknown[]) => ({ or: conds }), + relations: vi.fn(() => ({})), })); import { diff --git a/packages/api/src/services/__tests__/featureAccessService.test.ts b/packages/api/src/services/__tests__/featureAccessService.test.ts index 7734f642b3..19c38cf09f 100644 --- a/packages/api/src/services/__tests__/featureAccessService.test.ts +++ b/packages/api/src/services/__tests__/featureAccessService.test.ts @@ -43,7 +43,10 @@ const mocks = vi.hoisted(() => { vi.mock('@packrat/api/db', () => ({ createDb: mocks.createDb })); vi.mock('@packrat/api/utils/sentry', () => ({ captureApiException: mocks.captureApiException })); vi.mock('@packrat/db', () => ({ featureAccess: { key: 'key' } })); -vi.mock('drizzle-orm', () => ({ eq: vi.fn((col, val) => ({ col, val })) })); +vi.mock('drizzle-orm', () => ({ + eq: vi.fn((col, val) => ({ col, val })), + relations: vi.fn(() => ({})), +})); import { canAccessFeature, diff --git a/packages/api/src/services/__tests__/featureFlagsService.test.ts b/packages/api/src/services/__tests__/featureFlagsService.test.ts index 91591892d2..531197d586 100644 --- a/packages/api/src/services/__tests__/featureFlagsService.test.ts +++ b/packages/api/src/services/__tests__/featureFlagsService.test.ts @@ -34,7 +34,10 @@ const mocks = vi.hoisted(() => { vi.mock('@packrat/api/db', () => ({ createDb: mocks.createDb })); vi.mock('@packrat/api/utils/sentry', () => ({ captureApiException: mocks.captureApiException })); vi.mock('@packrat/db', () => ({ featureFlags: { key: 'key' } })); -vi.mock('drizzle-orm', () => ({ eq: vi.fn((col, val) => ({ col, val })) })); +vi.mock('drizzle-orm', () => ({ + eq: vi.fn((col, val) => ({ col, val })), + relations: vi.fn(() => ({})), +})); import { APP_CONFIG, FeatureFlag } from '@packrat/config'; import { diff --git a/packages/api/src/services/__tests__/passwordResetService.test.ts b/packages/api/src/services/__tests__/passwordResetService.test.ts index d0486d15a0..779a2db3bc 100644 --- a/packages/api/src/services/__tests__/passwordResetService.test.ts +++ b/packages/api/src/services/__tests__/passwordResetService.test.ts @@ -50,10 +50,8 @@ vi.mock('@packrat/api/utils/email', () => ({ sendPasswordResetEmail: mocks.sendPasswordResetEmail, })); vi.mock('@packrat/api/utils/auth', () => ({ - timingSafeEqual: mocks.timingSafeEqual, -})); -vi.mock('@better-auth/utils/password', () => ({ hashPassword: mocks.hashPassword, + timingSafeEqual: mocks.timingSafeEqual, })); vi.mock('@packrat/db', () => ({ users: {}, @@ -64,6 +62,7 @@ vi.mock('drizzle-orm', () => ({ and: vi.fn(), eq: vi.fn(), gt: vi.fn(), + relations: vi.fn(() => ({})), })); import { requestPasswordReset, verifyOtpAndResetPassword } from '../passwordResetService'; diff --git a/packages/api/src/services/__tests__/userService.test.ts b/packages/api/src/services/__tests__/userService.test.ts index 3c2e25f2d5..f817d504d0 100644 --- a/packages/api/src/services/__tests__/userService.test.ts +++ b/packages/api/src/services/__tests__/userService.test.ts @@ -29,7 +29,10 @@ const mocks = vi.hoisted(() => { vi.mock('@packrat/api/db', () => ({ createDb: mocks.createDb })); vi.mock('@packrat/api/utils/auth', () => ({ hashPassword: mocks.hashPassword })); vi.mock('@packrat/db', () => ({ users: { email: 'email', id: 'id' } })); -vi.mock('drizzle-orm', () => ({ eq: vi.fn() })); +vi.mock('drizzle-orm', () => ({ + eq: vi.fn(), + relations: vi.fn(() => ({})), +})); import { UserService } from '../userService'; @@ -69,7 +72,7 @@ describe('UserService', () => { await service.findByEmail('ALICE@EXAMPLE.COM'); // UserService calls eq(users.email, email.toLowerCase()), which is called with the lowercased value const { eq } = await import('drizzle-orm'); - const { users } = await import('@packrat/db'); + const { users } = await import('@packrat/db/schema'); expect(vi.mocked(eq)).toHaveBeenCalledWith(users.email, 'alice@example.com'); }); }); diff --git a/packages/api/src/services/aiService.ts b/packages/api/src/services/aiService.ts index 3ae247f750..19bf9945d8 100644 --- a/packages/api/src/services/aiService.ts +++ b/packages/api/src/services/aiService.ts @@ -3,7 +3,6 @@ import { createPerplexityAIProvider } from '@packrat/api/utils/ai/provider'; import type { Env } from '@packrat/api/utils/env-validation'; import { getEnv } from '@packrat/api/utils/env-validation'; import { isFunction } from '@packrat/guards'; -import { generateText } from 'ai'; interface SearchResult { answer: string; @@ -35,6 +34,7 @@ export class AIService { }); try { + const { generateText } = await import('ai'); const resp = await generateText({ model: perplexity(DEFAULT_MODELS.PERPLEXITY_SEARCH), system: WEB_SEARCH_SYSTEM_PROMPT, diff --git a/packages/api/src/services/catalogService.ts b/packages/api/src/services/catalogService.ts index 930748a7cd..3fcf551e0a 100644 --- a/packages/api/src/services/catalogService.ts +++ b/packages/api/src/services/catalogService.ts @@ -7,7 +7,7 @@ import { catalogItemEtlJobs, catalogItems, type NewCatalogItem, -} from '@packrat/db'; +} from '@packrat/db/schema'; import { and, asc, diff --git a/packages/api/src/services/embeddingService.ts b/packages/api/src/services/embeddingService.ts index 65ad7b4097..be73e7157c 100644 --- a/packages/api/src/services/embeddingService.ts +++ b/packages/api/src/services/embeddingService.ts @@ -1,7 +1,6 @@ import { DEFAULT_MODELS } from '@packrat/api/utils/ai/models'; import { type AIProvider, createAIProvider } from '@packrat/api/utils/ai/provider'; import type { Env } from '@packrat/api/utils/env-validation'; -import { embed, embedMany } from 'ai'; // ── Embedding text normalization ────────────────────────────────────── const NEWLINE = /\n/g; @@ -55,6 +54,7 @@ export const generateEmbedding = async ( } const aiProvider = createAIProvider(providerConfig); + const { embed } = await import('ai'); const { embedding } = await embed({ model: aiProvider.embedding(DEFAULT_MODELS.OPENAI_EMBEDDING), @@ -84,6 +84,7 @@ export const generateManyEmbeddings = async ( } const aiProvider = createAIProvider(providerConfig); + const { embedMany } = await import('ai'); const { embeddings } = await embedMany({ model: aiProvider.embedding(DEFAULT_MODELS.OPENAI_EMBEDDING), diff --git a/packages/api/src/services/entitlementsService.ts b/packages/api/src/services/entitlementsService.ts index ff0f3a9b8e..f6ed7133a3 100644 --- a/packages/api/src/services/entitlementsService.ts +++ b/packages/api/src/services/entitlementsService.ts @@ -1,7 +1,7 @@ import { createDb } from '@packrat/api/db'; import { captureApiException } from '@packrat/api/utils/sentry'; import { PACKRAT_PRO_ENTITLEMENT } from '@packrat/config'; -import { entitlements } from '@packrat/db'; +import { entitlements } from '@packrat/db/schema'; import { asNumber, asString, isArray, isString } from '@packrat/guards'; import { and, eq, gt, isNull, or } from 'drizzle-orm'; diff --git a/packages/api/src/services/etl/CatalogItemValidator.ts b/packages/api/src/services/etl/CatalogItemValidator.ts index b3b178063d..25664016ca 100644 --- a/packages/api/src/services/etl/CatalogItemValidator.ts +++ b/packages/api/src/services/etl/CatalogItemValidator.ts @@ -1,5 +1,5 @@ import type { ValidatedCatalogItem } from '@packrat/api/types/etl'; -import type { NewCatalogItem } from '@packrat/db'; +import type { NewCatalogItem } from '@packrat/db/schema'; import { isNumber, isString } from '@packrat/guards'; import type { ValidationError } from '@packrat/schemas/validation'; diff --git a/packages/api/src/services/etl/mergeItemsBySku.ts b/packages/api/src/services/etl/mergeItemsBySku.ts index 21642f3eff..9b63670be1 100644 --- a/packages/api/src/services/etl/mergeItemsBySku.ts +++ b/packages/api/src/services/etl/mergeItemsBySku.ts @@ -1,4 +1,4 @@ -import type { NewCatalogItem } from '@packrat/db'; +import type { NewCatalogItem } from '@packrat/db/schema'; /** * Merges all occurrences of each SKU into a single item diff --git a/packages/api/src/services/etl/processCatalogEtl.ts b/packages/api/src/services/etl/processCatalogEtl.ts index 22ac80fb94..a4bba3a837 100644 --- a/packages/api/src/services/etl/processCatalogEtl.ts +++ b/packages/api/src/services/etl/processCatalogEtl.ts @@ -2,7 +2,7 @@ import { createDbClient } from '@packrat/api/db'; import { mapCsvRowToItem } from '@packrat/api/utils/csv-utils'; import type { Env } from '@packrat/api/utils/env-validation'; import { isJsonlFile, mapJsonRowToItem } from '@packrat/api/utils/json-utils'; -import { etlJobs, type NewCatalogItem, type NewInvalidItemLog } from '@packrat/db'; +import { etlJobs, type NewCatalogItem, type NewInvalidItemLog } from '@packrat/db/schema'; import { toRecord } from '@packrat/guards'; import { safeJsonParse } from '@packrat/utils'; import { parse } from 'csv-parse'; diff --git a/packages/api/src/services/etl/processLogsBatch.ts b/packages/api/src/services/etl/processLogsBatch.ts index ceace7db89..3f485428c5 100644 --- a/packages/api/src/services/etl/processLogsBatch.ts +++ b/packages/api/src/services/etl/processLogsBatch.ts @@ -2,7 +2,7 @@ import { createDbClient } from '@packrat/api/db'; import type { Env } from '@packrat/api/utils/env-validation'; import { logger } from '@packrat/api/utils/logger'; import { record } from '@packrat/api/utils/sentry'; -import { invalidItemLogs, type NewInvalidItemLog } from '@packrat/db'; +import { invalidItemLogs, type NewInvalidItemLog } from '@packrat/db/schema'; import { updateEtlJobProgress } from './updateEtlJobProgress'; export async function processLogsBatch({ diff --git a/packages/api/src/services/etl/processValidItemsBatch.ts b/packages/api/src/services/etl/processValidItemsBatch.ts index 25f33abcac..2de925401f 100644 --- a/packages/api/src/services/etl/processValidItemsBatch.ts +++ b/packages/api/src/services/etl/processValidItemsBatch.ts @@ -2,7 +2,7 @@ import { createDbClient } from '@packrat/api/db'; import { getEmbeddingText } from '@packrat/api/utils/embeddingHelper'; import type { Env } from '@packrat/api/utils/env-validation'; import { logger } from '@packrat/api/utils/logger'; -import { etlJobs, type NewCatalogItem } from '@packrat/db'; +import { etlJobs, type NewCatalogItem } from '@packrat/db/schema'; import { isString } from '@packrat/guards'; import { eq, sql } from 'drizzle-orm'; import { CatalogService } from '../catalogService'; diff --git a/packages/api/src/services/etl/updateEtlJobProgress.ts b/packages/api/src/services/etl/updateEtlJobProgress.ts index a30d2041a3..78e9b13565 100644 --- a/packages/api/src/services/etl/updateEtlJobProgress.ts +++ b/packages/api/src/services/etl/updateEtlJobProgress.ts @@ -1,6 +1,6 @@ import { createDbClient } from '@packrat/api/db'; import type { Env } from '@packrat/api/utils/env-validation'; -import { etlJobs } from '@packrat/db'; +import { etlJobs } from '@packrat/db/schema'; import { eq, sql } from 'drizzle-orm'; export async function updateEtlJobProgress({ diff --git a/packages/api/src/services/featureAccessService.ts b/packages/api/src/services/featureAccessService.ts index 7b048a6ad5..688989a3db 100644 --- a/packages/api/src/services/featureAccessService.ts +++ b/packages/api/src/services/featureAccessService.ts @@ -1,7 +1,7 @@ import { createDb } from '@packrat/api/db'; import { captureApiException } from '@packrat/api/utils/sentry'; import { hasFeatureAccess } from '@packrat/config'; -import { type FeatureAccess, featureAccess } from '@packrat/db'; +import { type FeatureAccess, featureAccess } from '@packrat/db/schema'; import { eq } from 'drizzle-orm'; /** diff --git a/packages/api/src/services/featureFlagsService.ts b/packages/api/src/services/featureFlagsService.ts index e996e07119..24bc8139a8 100644 --- a/packages/api/src/services/featureFlagsService.ts +++ b/packages/api/src/services/featureFlagsService.ts @@ -1,7 +1,7 @@ import { createDb } from '@packrat/api/db'; import { captureApiException } from '@packrat/api/utils/sentry'; import { APP_CONFIG, FeatureFlag } from '@packrat/config'; -import { featureFlags } from '@packrat/db'; +import { featureFlags } from '@packrat/db/schema'; import { eq } from 'drizzle-orm'; // All flag keys known to the codebase. Exported so the admin route can build diff --git a/packages/api/src/services/imageDetectionService.ts b/packages/api/src/services/imageDetectionService.ts index 7d63cee306..98338ed0d3 100644 --- a/packages/api/src/services/imageDetectionService.ts +++ b/packages/api/src/services/imageDetectionService.ts @@ -1,8 +1,7 @@ import { DEFAULT_MODELS } from '@packrat/api/utils/ai/models'; import { createAIProvider } from '@packrat/api/utils/ai/provider'; import { getEnv } from '@packrat/api/utils/env-validation'; -import type { CatalogItem } from '@packrat/db'; -import { generateObject } from 'ai'; +import type { CatalogItem } from '@packrat/db/schema'; import { z } from 'zod'; import { CatalogService } from './catalogService'; @@ -72,6 +71,7 @@ export class ImageDetectionService { cloudflareAiBinding: AI, }); + const { generateObject } = await import('ai'); const { object } = await generateObject({ model: aiProvider(DEFAULT_MODELS.OPENAI_CHAT), schema: imageAnalysisSchema, diff --git a/packages/api/src/services/packItemService.ts b/packages/api/src/services/packItemService.ts index 0a9cf13fcc..24c5bd4b5b 100644 --- a/packages/api/src/services/packItemService.ts +++ b/packages/api/src/services/packItemService.ts @@ -1,5 +1,5 @@ import { createDb } from '@packrat/api/db'; -import { packItems } from '@packrat/db'; +import { packItems } from '@packrat/db/schema'; import { and, eq } from 'drizzle-orm'; export class PackItemService { diff --git a/packages/api/src/services/packService.ts b/packages/api/src/services/packService.ts index de51e9b5c0..ba62e93860 100644 --- a/packages/api/src/services/packService.ts +++ b/packages/api/src/services/packService.ts @@ -4,8 +4,7 @@ import { createAIProvider } from '@packrat/api/utils/ai/provider'; import { getEnv } from '@packrat/api/utils/env-validation'; import { setQueryTag } from '@packrat/api/utils/queryMetrics'; import { PACK_CATEGORIES } from '@packrat/constants'; -import { type NewPack, type NewPackItem, packItems, packs } from '@packrat/db'; -import { generateObject } from 'ai'; +import { type NewPack, type NewPackItem, packItems, packs } from '@packrat/db/schema'; import { and, eq } from 'drizzle-orm'; import { z } from 'zod'; import { computePackWeights } from '../utils/compute-pack'; @@ -152,6 +151,7 @@ export class PackService { cloudflareAiBinding: AI, }); + const { generateObject } = await import('ai'); const { object } = await generateObject({ model: aiProvider(DEFAULT_MODELS.OPENAI_CHAT), output: 'array', diff --git a/packages/api/src/services/passwordResetService.ts b/packages/api/src/services/passwordResetService.ts index 399708c635..aa61c6b0c0 100644 --- a/packages/api/src/services/passwordResetService.ts +++ b/packages/api/src/services/passwordResetService.ts @@ -1,8 +1,7 @@ -import { hashPassword } from '@better-auth/utils/password'; import { createDb } from '@packrat/api/db'; -import { timingSafeEqual } from '@packrat/api/utils/auth'; +import { hashPassword, timingSafeEqual } from '@packrat/api/utils/auth'; import { sendPasswordResetEmail } from '@packrat/api/utils/email'; -import { account, users, verification } from '@packrat/db'; +import { account, users, verification } from '@packrat/db/schema'; import { and, eq, gt } from 'drizzle-orm'; const OTP_LENGTH = 6; diff --git a/packages/api/src/services/retention/invalidLogRetention.ts b/packages/api/src/services/retention/invalidLogRetention.ts index 50295d4806..1bfc3d3322 100644 --- a/packages/api/src/services/retention/invalidLogRetention.ts +++ b/packages/api/src/services/retention/invalidLogRetention.ts @@ -6,7 +6,7 @@ import { createDbClient } from '@packrat/api/db'; import type { Env } from '@packrat/api/utils/env-validation'; import { record } from '@packrat/api/utils/sentry'; -import { invalidItemLogs } from '@packrat/db'; +import { invalidItemLogs } from '@packrat/db/schema'; import { inArray, lt, sql } from 'drizzle-orm'; const DEFAULT_RETENTION_DAYS = 90; diff --git a/packages/api/src/services/userService.ts b/packages/api/src/services/userService.ts index 626f1feb5c..1eed3d7113 100644 --- a/packages/api/src/services/userService.ts +++ b/packages/api/src/services/userService.ts @@ -1,6 +1,6 @@ import { createDb } from '@packrat/api/db'; import { hashPassword } from '@packrat/api/utils/auth'; -import { type User, users } from '@packrat/db'; +import { type User, users } from '@packrat/db/schema'; import { eq } from 'drizzle-orm'; export type CreateUserInput = { diff --git a/packages/api/src/services/wildlifeIdentificationService.ts b/packages/api/src/services/wildlifeIdentificationService.ts index 7e365d6d6d..18fee82469 100644 --- a/packages/api/src/services/wildlifeIdentificationService.ts +++ b/packages/api/src/services/wildlifeIdentificationService.ts @@ -1,7 +1,6 @@ import { DEFAULT_MODELS } from '@packrat/api/utils/ai/models'; import { createAIProvider } from '@packrat/api/utils/ai/provider'; import { getEnv } from '@packrat/api/utils/env-validation'; -import { generateObject } from 'ai'; import { z } from 'zod'; const SPECIES_IDENTIFICATION_SYSTEM_PROMPT = `You are an expert naturalist and wildlife biologist specializing in plant and animal identification. @@ -75,6 +74,7 @@ export class WildlifeIdentificationService { }); try { + const { generateObject } = await import('ai'); const { object } = await generateObject({ model: aiProvider(DEFAULT_MODELS.OPENAI_CHAT), schema: identificationResponseSchema, diff --git a/packages/api/src/types/etl.ts b/packages/api/src/types/etl.ts index 5e588cc69e..07b5178efe 100644 --- a/packages/api/src/types/etl.ts +++ b/packages/api/src/types/etl.ts @@ -1,4 +1,4 @@ -import type { NewCatalogItem } from '@packrat/db'; +import type { NewCatalogItem } from '@packrat/db/schema'; import type { ValidationError } from '@packrat/schemas/validation'; export interface ValidatedCatalogItem { diff --git a/packages/api/src/utils/DbUtils.ts b/packages/api/src/utils/DbUtils.ts index 060382ce53..33d96270f6 100644 --- a/packages/api/src/utils/DbUtils.ts +++ b/packages/api/src/utils/DbUtils.ts @@ -1,5 +1,5 @@ import { createDb } from '@packrat/api/db'; -import { catalogItems, packs } from '@packrat/db'; +import { catalogItems, packs } from '@packrat/db/schema'; import { and, arrayOverlaps, eq, inArray, type SQL, sql } from 'drizzle-orm'; // Get pack details from the database diff --git a/packages/api/src/utils/__tests__/compute-pack.test.ts b/packages/api/src/utils/__tests__/compute-pack.test.ts index 00d1f1b153..54f1676262 100644 --- a/packages/api/src/utils/__tests__/compute-pack.test.ts +++ b/packages/api/src/utils/__tests__/compute-pack.test.ts @@ -1,4 +1,4 @@ -import type { PackItem, PackWithItems } from '@packrat/db'; +import type { PackItem, PackWithItems } from '@packrat/db/schema'; import { describe, expect, it } from 'vitest'; import { computePackBreakdown, computePacksWeights, computePackWeights } from '../compute-pack'; diff --git a/packages/api/src/utils/__tests__/env-validation.test.ts b/packages/api/src/utils/__tests__/env-validation.test.ts index 0903f11b32..6a9b4b5b80 100644 --- a/packages/api/src/utils/__tests__/env-validation.test.ts +++ b/packages/api/src/utils/__tests__/env-validation.test.ts @@ -130,7 +130,9 @@ describe('env-validation', () => { const result = apiEnvSchema.safeParse(makeRawEnv({ OPENAI_API_KEY: undefined })); expect(result.success).toBe(false); if (!result.success) { - expect(result.error.message).toContain('Required'); + expect(result.error.issues).toEqual([ + expect.objectContaining({ path: ['OPENAI_API_KEY'] }), + ]); } }); @@ -140,7 +142,9 @@ describe('env-validation', () => { ); expect(result.success).toBe(false); if (!result.success) { - expect(result.error.message).toContain('Required'); + expect(result.error.issues).toEqual([ + expect.objectContaining({ path: ['GOOGLE_GENERATIVE_AI_API_KEY'] }), + ]); } }); diff --git a/packages/api/src/utils/ai/tools.ts b/packages/api/src/utils/ai/tools.ts index 179b576f23..6b35674b91 100644 --- a/packages/api/src/utils/ai/tools.ts +++ b/packages/api/src/utils/ai/tools.ts @@ -1,9 +1,9 @@ import { AIService, CatalogService, WeatherService } from '@packrat/api/services'; import { executeSqlAiTool } from '@packrat/api/services/executeSqlAiTool'; -import { tool } from 'ai'; import { z } from 'zod'; -export function createTools(userId: string) { +export async function createTools(userId: string) { + const { tool } = await import('ai'); const weatherService = new WeatherService(); const catalogService = new CatalogService(); const aiService = new AIService(); diff --git a/packages/api/src/utils/compute-pack.ts b/packages/api/src/utils/compute-pack.ts index b7f48949a5..76c325c3cd 100644 --- a/packages/api/src/utils/compute-pack.ts +++ b/packages/api/src/utils/compute-pack.ts @@ -3,7 +3,7 @@ import type { PackForWeights, PackItemForBreakdown, PackItemForWeights, -} from '@packrat/db'; +} from '@packrat/db/schema'; import type { WeightUnit } from '@packrat/units'; import { displayWeight, normalize, parseWeightUnit } from '@packrat/units'; diff --git a/packages/api/src/utils/csv-utils.ts b/packages/api/src/utils/csv-utils.ts index 0138d1caf8..c808775377 100644 --- a/packages/api/src/utils/csv-utils.ts +++ b/packages/api/src/utils/csv-utils.ts @@ -1,4 +1,4 @@ -import type { NewCatalogItem } from '@packrat/db'; +import type { NewCatalogItem } from '@packrat/db/schema'; import { isString } from '@packrat/guards'; import { AvailabilitySchema, WeightUnitSchema } from '@packrat/schemas/constants'; import { safeJsonParse } from '@packrat/utils'; diff --git a/packages/api/src/utils/email.ts b/packages/api/src/utils/email.ts index 0dba143edf..480fc6e7bc 100644 --- a/packages/api/src/utils/email.ts +++ b/packages/api/src/utils/email.ts @@ -1,5 +1,4 @@ import { getEnv } from '@packrat/api/utils/env-validation'; -import { Resend } from 'resend'; export async function sendEmail({ to, @@ -11,6 +10,7 @@ export async function sendEmail({ html: string; }): Promise { const { RESEND_API_KEY, EMAIL_FROM } = getEnv(); + const { Resend } = await import('resend'); const resendClient = new Resend(RESEND_API_KEY); await resendClient.emails.send({ diff --git a/packages/api/src/utils/embeddingHelper.ts b/packages/api/src/utils/embeddingHelper.ts index 4578387219..3a800d67c7 100644 --- a/packages/api/src/utils/embeddingHelper.ts +++ b/packages/api/src/utils/embeddingHelper.ts @@ -1,4 +1,4 @@ -import type { CatalogItem, PackItem } from '@packrat/db'; +import type { CatalogItem, PackItem } from '@packrat/db/schema'; import { isObject, isString } from '@packrat/guards'; type ItemForEmbedding = Partial | Partial; diff --git a/packages/api/src/utils/getPresignedUrl.ts b/packages/api/src/utils/getPresignedUrl.ts index aacc13416b..14a6d6c2a3 100644 --- a/packages/api/src/utils/getPresignedUrl.ts +++ b/packages/api/src/utils/getPresignedUrl.ts @@ -1,13 +1,23 @@ -import { type GetObjectCommand, type PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; -import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import type { + GetObjectCommand, + PutObjectCommand, + S3Client as S3ClientType, +} from '@aws-sdk/client-s3'; +import type { getSignedUrl as getSignedUrlType } from '@aws-sdk/s3-request-presigner'; import { getEnv } from './env-validation'; +type GetSignedUrlOptions = Parameters[2]; + export async function getPresignedUrl(opts: { command: GetObjectCommand | PutObjectCommand; - signOptions: Parameters[2]; + signOptions: GetSignedUrlOptions; }): Promise { const { command, signOptions } = opts; const { R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, CLOUDFLARE_ACCOUNT_ID } = getEnv(); + const [{ S3Client }, { getSignedUrl }] = (await Promise.all([ + import('@aws-sdk/client-s3'), + import('@aws-sdk/s3-request-presigner'), + ])) as [{ S3Client: typeof S3ClientType }, { getSignedUrl: typeof getSignedUrlType }]; const s3Client = new S3Client({ region: 'auto', diff --git a/packages/api/src/utils/json-utils.ts b/packages/api/src/utils/json-utils.ts index ce17e4b6d5..67f125db72 100644 --- a/packages/api/src/utils/json-utils.ts +++ b/packages/api/src/utils/json-utils.ts @@ -1,5 +1,5 @@ import { parseCatalogJson, parseFaqs, parsePrice, parseWeight } from '@packrat/api/utils/csv-utils'; -import type { NewCatalogItem } from '@packrat/db'; +import type { NewCatalogItem } from '@packrat/db/schema'; import { isNumber, isObject, isString, toStringRecord } from '@packrat/guards'; import { AvailabilitySchema, WeightUnitSchema } from '@packrat/schemas/constants'; import { safeJsonParse } from '@packrat/utils'; diff --git a/packages/api/src/workflows/catalog-etl-workflow.ts b/packages/api/src/workflows/catalog-etl-workflow.ts index 9cffef8ddc..3d6ba8a7fb 100644 --- a/packages/api/src/workflows/catalog-etl-workflow.ts +++ b/packages/api/src/workflows/catalog-etl-workflow.ts @@ -34,7 +34,7 @@ import { queryMetricsAls, } from '@packrat/api/utils/queryMetrics'; import { record } from '@packrat/api/utils/sentry'; -import { etlJobs, type NewCatalogItem, type NewInvalidItemLog } from '@packrat/db'; +import { etlJobs, type NewCatalogItem, type NewInvalidItemLog } from '@packrat/db/schema'; import { toRecord } from '@packrat/guards'; import { safeJsonParse } from '@packrat/utils'; import { parse } from 'csv-parse'; diff --git a/packages/api/test/etl.test.ts b/packages/api/test/etl.test.ts index 6cb5bb639c..2bf33b4476 100644 --- a/packages/api/test/etl.test.ts +++ b/packages/api/test/etl.test.ts @@ -3,7 +3,7 @@ import { CatalogService } from '@packrat/api/services'; import { processCatalogETL } from '@packrat/api/services/etl/processCatalogEtl'; import { processValidItemsBatch } from '@packrat/api/services/etl/processValidItemsBatch'; import { R2BucketService } from '@packrat/api/services/r2-bucket'; -import { catalogItems, etlJobs, invalidItemLogs } from '@packrat/db'; +import { catalogItems, etlJobs, invalidItemLogs } from '@packrat/db/schema'; import { count, eq } from 'drizzle-orm'; import { describe, expect, it, vi } from 'vitest'; diff --git a/packages/api/test/feature-access.test.ts b/packages/api/test/feature-access.test.ts index 54f5659965..489225022d 100644 --- a/packages/api/test/feature-access.test.ts +++ b/packages/api/test/feature-access.test.ts @@ -1,6 +1,6 @@ import { createDbClient } from '@packrat/api/db'; import { canAccessFeature } from '@packrat/api/services'; -import { featureAccess } from '@packrat/db'; +import { featureAccess } from '@packrat/db/schema'; import { describe, expect, it } from 'vitest'; import { app } from '../src/index'; diff --git a/packages/api/test/fixtures/catalog-fixtures.ts b/packages/api/test/fixtures/catalog-fixtures.ts index a7eb9c7c23..52394e7bb1 100644 --- a/packages/api/test/fixtures/catalog-fixtures.ts +++ b/packages/api/test/fixtures/catalog-fixtures.ts @@ -1,4 +1,4 @@ -import type { catalogItems } from '@packrat/db'; +import type { catalogItems } from '@packrat/db/schema'; import type { InferInsertModel } from 'drizzle-orm'; /** diff --git a/packages/api/test/fixtures/pack-fixtures.ts b/packages/api/test/fixtures/pack-fixtures.ts index ea1ae72c50..8905ace583 100644 --- a/packages/api/test/fixtures/pack-fixtures.ts +++ b/packages/api/test/fixtures/pack-fixtures.ts @@ -1,4 +1,4 @@ -import type { packItems, packs } from '@packrat/db'; +import type { packItems, packs } from '@packrat/db/schema'; import type { InferInsertModel } from 'drizzle-orm'; type PackOverrides = Partial> & { userId: string }; diff --git a/packages/api/test/fixtures/pack-template-fixtures.ts b/packages/api/test/fixtures/pack-template-fixtures.ts index 43c8c64eab..3eec9d6abf 100644 --- a/packages/api/test/fixtures/pack-template-fixtures.ts +++ b/packages/api/test/fixtures/pack-template-fixtures.ts @@ -1,4 +1,4 @@ -import type { packTemplateItems, packTemplates } from '@packrat/db'; +import type { packTemplateItems, packTemplates } from '@packrat/db/schema'; import type { InferInsertModel } from 'drizzle-orm'; type PackTemplateOverrides = Partial> & { userId: string }; diff --git a/packages/api/test/packs.test.ts b/packages/api/test/packs.test.ts index 3e50d2389f..f8c56b697c 100644 --- a/packages/api/test/packs.test.ts +++ b/packages/api/test/packs.test.ts @@ -1,7 +1,7 @@ import { createDb } from '@packrat/api/db'; import { PackService } from '@packrat/api/services/packService'; -import type { Pack } from '@packrat/db'; -import { packItems, packs } from '@packrat/db'; +import type { Pack } from '@packrat/db/schema'; +import { packItems, packs } from '@packrat/db/schema'; import { eq } from 'drizzle-orm'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { diff --git a/packages/api/test/utils/db-helpers.ts b/packages/api/test/utils/db-helpers.ts index b0154fff2b..7dfb22ccdc 100644 --- a/packages/api/test/utils/db-helpers.ts +++ b/packages/api/test/utils/db-helpers.ts @@ -1,5 +1,6 @@ import { createDb } from '@packrat/api/db'; import { hashPassword } from '@packrat/api/utils/auth'; +import * as schema from '@packrat/db/schema'; import { catalogItems, packItems, @@ -7,8 +8,7 @@ import { packTemplateItems, packTemplates, type users, -} from '@packrat/db'; -import * as schema from '@packrat/db/schema'; +} from '@packrat/db/schema'; import { assertDefined } from '@packrat/guards'; import type { InferInsertModel } from 'drizzle-orm'; import { createFatCatalogItem, createTestCatalogItem } from '../fixtures/catalog-fixtures'; diff --git a/packages/api/test/utils/user-helpers.ts b/packages/api/test/utils/user-helpers.ts index 55a4b9d163..551b01feba 100644 --- a/packages/api/test/utils/user-helpers.ts +++ b/packages/api/test/utils/user-helpers.ts @@ -1,6 +1,6 @@ import { createDb } from '@packrat/api/db'; import { hashPassword } from '@packrat/api/utils/auth'; -import { users } from '@packrat/db'; +import { users } from '@packrat/db/schema'; import type { InferInsertModel } from 'drizzle-orm'; /** From 549f5b19a14557a0757195b1e1bebeaa7c4adc6d Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:39:15 -0600 Subject: [PATCH 74/81] =?UTF-8?q?=F0=9F=A7=AA=20test(swift):=20harden=20vi?= =?UTF-8?q?sual=20screenshot=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Weather/WeatherViewModel.swift | 3 +- .../Tests/PackRatTests/ViewModelTests.swift | 6 +- .../VisualScreenshotTests.swift | 76 +++++++++++++++++-- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/apps/swift/Sources/PackRat/Features/Weather/WeatherViewModel.swift b/apps/swift/Sources/PackRat/Features/Weather/WeatherViewModel.swift index 1c501fcf16..9f38f220c6 100644 --- a/apps/swift/Sources/PackRat/Features/Weather/WeatherViewModel.swift +++ b/apps/swift/Sources/PackRat/Features/Weather/WeatherViewModel.swift @@ -19,12 +19,13 @@ final class WeatherViewModel { private let service: any WeatherServicing private var searchTask: Task? - init(service: any WeatherServicing = WeatherService.shared) { + init(service: any WeatherServicing = WeatherService.shared, loadPersistedState: Bool = true) { self.service = service if VisualSampleData.isUITestFixturesEnabled { UserDefaults.standard.removeObject(forKey: savedLocationsKey) UserDefaults.standard.removeObject(forKey: activeLocationKey) } + guard loadPersistedState else { return } guard !VisualSampleData.isScreenshotCapture else { return } loadSavedLocations() if let active = savedLocations.first(where: { $0.id == UserDefaults.standard.integer(forKey: activeLocationKey) }) diff --git a/apps/swift/Tests/PackRatTests/ViewModelTests.swift b/apps/swift/Tests/PackRatTests/ViewModelTests.swift index 69475d1ee5..784a740258 100644 --- a/apps/swift/Tests/PackRatTests/ViewModelTests.swift +++ b/apps/swift/Tests/PackRatTests/ViewModelTests.swift @@ -152,7 +152,7 @@ struct TripsViewModelTests { struct WeatherViewModelTests { @Test("onSearchTextChanged clears results when empty") @MainActor func clearsResultsWhenEmpty() { - let vm = WeatherViewModel() + let vm = WeatherViewModel(loadPersistedState: false) vm.searchResults = [WeatherLocation(id: 1, name: "Denver", region: nil, country: nil, lat: nil, lon: nil)] vm.searchText = "" vm.onSearchTextChanged() @@ -161,7 +161,7 @@ struct WeatherViewModelTests { @Test("searchText below 2 chars does not search") @MainActor func shortQuerySkipsSearch() { - let vm = WeatherViewModel() + let vm = WeatherViewModel(loadPersistedState: false) vm.searchText = "D" vm.onSearchTextChanged() #expect(vm.searchResults.isEmpty) @@ -170,7 +170,7 @@ struct WeatherViewModelTests { @Test("selectLocation falls back to by-name forecast when id lookup fails") @MainActor func forecastFallsBackToByName() async { let service = FallbackWeatherService() - let vm = WeatherViewModel(service: service) + let vm = WeatherViewModel(service: service, loadPersistedState: false) let denver = WeatherLocation( id: 5419384, name: "Denver", diff --git a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift index 6738ff58ea..876c20a225 100644 --- a/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift +++ b/apps/swift/Tests/PackRatUITests/VisualScreenshotTests.swift @@ -445,6 +445,7 @@ final class VisualScreenshotTests: XCTestCase { resetPhoneModalState(mode) captureTab("Packs", name: "\(prefix)-packs-before-new-pack") + resetPacksToMyPacksMode() tapAndCapture(identifier: "packs_new_pack_button", fallbackButton: "New Pack", name: "\(prefix)-new-pack-sheet") resetPhoneModalState(mode) @@ -592,9 +593,7 @@ final class VisualScreenshotTests: XCTestCase { } private func captureTab(_ label: String, name: String, beforeCapture: (() -> Void)? = nil) { - let tab = app.tabBars.buttons[label] - XCTAssertTrue(tab.waitForExistence(timeout: 5), "Expected tab '\(label)' for screenshot \(name)") - tab.tap() + openPrimarySurface(label, screenshotName: name) beforeCapture?() capture(name) } @@ -649,7 +648,7 @@ final class VisualScreenshotTests: XCTestCase { smallScrollUp() } } - if let visibleCandidate, visibleCandidate.exists, visibleCandidate.isHittable { + if let visibleCandidate, visibleCandidate.exists { activate(visibleCandidate) verifyAndCapture() return @@ -698,13 +697,69 @@ final class VisualScreenshotTests: XCTestCase { } private func openHomeForActionBaseline(name: String) { - let tab = app.tabBars.buttons["Home"] - XCTAssertTrue(tab.waitForExistence(timeout: 5), "Expected tab 'Home' for screenshot \(name)") - tab.tap() + openPrimarySurface("Home", screenshotName: name) resetActiveHomeSearchPresentation() capture(name) } + private func openPrimarySurface(_ label: String, screenshotName: String) { + #if os(iOS) + if isPadVisualRun { + let sidebarButton = app.buttons["nav_\(label.lowercased().replacingOccurrences(of: " ", with: "_"))"] + if sidebarButton.waitForExistence(timeout: 3) { + activate(sidebarButton) + return + } + } + + dismissKeyboardIfNeeded() + + let tab = app.tabBars.buttons[label] + XCTAssertTrue(tab.waitForExistence(timeout: 5), "Expected tab '\(label)' for screenshot \(screenshotName)") + tapTabBarButton(tab) + #elseif os(macOS) + let identifier = "nav_\(label.lowercased().replacingOccurrences(of: " ", with: "_"))" + let sidebarButton = app.buttons[identifier] + if sidebarButton.waitForExistence(timeout: 3) { + activate(sidebarButton) + return + } + + let sidebarText = app.staticTexts[label] + XCTAssertTrue(sidebarText.waitForExistence(timeout: 5), "Expected sidebar item '\(label)' for screenshot \(screenshotName)") + activate(sidebarText) + #endif + } + + private func dismissKeyboardIfNeeded() { + #if os(iOS) + guard app.keyboards.firstMatch.exists else { return } + if app.buttons["Cancel"].exists { + app.buttons["Cancel"].tap() + } + if app.buttons["Close"].exists { + app.buttons["Close"].tap() + } + #endif + } + + private func tapTabBarButton(_ element: XCUIElement) { + #if os(iOS) + let frame = element.frame + guard frame.width > 0, frame.height > 0 else { + activate(element) + return + } + let coordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0)) + .withOffset(CGVector(dx: frame.midX, dy: frame.midY)) + coordinate.tap() + Thread.sleep(forTimeInterval: 0.35) + coordinate.tap() + #else + activate(element) + #endif + } + private func replaceHomeSearchText(_ text: String, in searchField: XCUIElement) { clearHomeSearchText(in: searchField) searchField.typeText(text) @@ -760,6 +815,13 @@ final class VisualScreenshotTests: XCTestCase { #endif } + private func resetPacksToMyPacksMode() { + let myPacks = app.buttons["packs_mode_my_packs"] + if myPacks.waitForExistence(timeout: 1), myPacks.isHittable { + myPacks.tap() + } + } + private func actionIsClearOfBottomBar(_ element: XCUIElement) -> Bool { #if os(iOS) element.frame.minY > 140 && element.frame.midY < app.frame.maxY - 170 From b50a9e8b7d34e1bd7f7a1f3b270fbde44f349dc8 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:47:17 -0600 Subject: [PATCH 75/81] =?UTF-8?q?=F0=9F=A7=B9=20chore(api):=20satisfy=20re?= =?UTF-8?q?adiness=20lint=20gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api/migrate.ts | 7 +++++-- packages/api/src/auth/local-e2e.ts | 3 ++- packages/api/src/e2e-worker.ts | 8 ++++++-- packages/api/src/routes/chat.ts | 7 ++++--- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/api/migrate.ts b/packages/api/migrate.ts index 4353b7479d..5e4f28461a 100644 --- a/packages/api/migrate.ts +++ b/packages/api/migrate.ts @@ -4,6 +4,7 @@ import { setTimeout as sleep } from 'node:timers/promises'; import { fileURLToPath } from 'node:url'; import { neon, neonConfig } from '@neondatabase/serverless'; import { nodeEnv } from '@packrat/env/node'; +import { safeJsonParse } from '@packrat/utils'; import { drizzle as drizzleBunSQL } from 'drizzle-orm/bun-sql'; import { migrate as migrateBunSQL } from 'drizzle-orm/bun-sql/migrator'; import { drizzle } from 'drizzle-orm/neon-http'; @@ -37,9 +38,11 @@ const isStandardPostgresUrl = (url: string) => { }; function expectedMigrationCount() { - const journal = JSON.parse( + const journal = safeJsonParse( readFileSync(join(__dirname, 'drizzle/meta/_journal.json'), 'utf8'), - ) as { entries?: unknown[] }; + ) as { + entries?: unknown[]; + }; return journal.entries?.length ?? 0; } diff --git a/packages/api/src/auth/local-e2e.ts b/packages/api/src/auth/local-e2e.ts index 0446a30f96..dd2e5168ce 100644 --- a/packages/api/src/auth/local-e2e.ts +++ b/packages/api/src/auth/local-e2e.ts @@ -1,4 +1,5 @@ import type { ValidatedEnv } from '@packrat/api/utils/env-validation'; +import { isString } from '@packrat/guards'; const bearerPrefixRegex = /^Bearer\s+/i; @@ -19,7 +20,7 @@ export type LocalE2EUser = { export function isLocalE2EAuthEnabled(env: ValidatedEnv): boolean { const dbUrl = env.NEON_DATABASE_URL; return ( - typeof dbUrl === 'string' && + isString(dbUrl) && (dbUrl.includes('127.0.0.1') || dbUrl.includes('localhost')) && Boolean(env.E2E_TEST_EMAIL) && Boolean(env.E2E_TEST_PASSWORD) && diff --git a/packages/api/src/e2e-worker.ts b/packages/api/src/e2e-worker.ts index 8ad1a8ff8d..8ccd1dcc68 100644 --- a/packages/api/src/e2e-worker.ts +++ b/packages/api/src/e2e-worker.ts @@ -51,7 +51,11 @@ function wellKnownMetadataKind(pathname: string): 'openid' | 'authorization-serv return null; } -async function handleLocalE2EAuth(request: Request, env: Env): Promise { +async function handleLocalE2EAuth(input: { + request: Request; + env: Env; +}): Promise { + const { request, env } = input; if (!isLocalE2EAuthEnabled(env)) return undefined; const url = new URL(request.url); @@ -112,7 +116,7 @@ export default { const preflight = corsPreflightResponse(request); if (preflight) return preflight; - const localAuthResponse = await handleLocalE2EAuth(request, e); + const localAuthResponse = await handleLocalE2EAuth({ request, env: e }); if (localAuthResponse) return addCorsHeaders({ request, response: localAuthResponse }); const getAuth = await loadAuth(); diff --git a/packages/api/src/routes/chat.ts b/packages/api/src/routes/chat.ts index 1744acd63b..c482c69a1e 100644 --- a/packages/api/src/routes/chat.ts +++ b/packages/api/src/routes/chat.ts @@ -10,6 +10,7 @@ import { CreateReportRequestSchema, UpdateReportStatusRequestSchema, } from '@packrat/schemas/chat'; +import { safeJsonStringify } from '@packrat/utils'; import type { UIMessage } from 'ai'; import { eq } from 'drizzle-orm'; import { Elysia, status } from 'elysia'; @@ -28,17 +29,17 @@ function e2eChatStreamResponse() { const encoder = new TextEncoder(); controller.enqueue( encoder.encode( - `data: ${JSON.stringify({ type: 'text-start', id: 'e2e-chat-response' })}\n\n`, + `data: ${safeJsonStringify({ type: 'text-start', id: 'e2e-chat-response' })}\n\n`, ), ); controller.enqueue( encoder.encode( - `data: ${JSON.stringify({ type: 'text-delta', id: 'e2e-chat-response', delta: text })}\n\n`, + `data: ${safeJsonStringify({ type: 'text-delta', id: 'e2e-chat-response', delta: text })}\n\n`, ), ); controller.enqueue( encoder.encode( - `data: ${JSON.stringify({ type: 'text-end', id: 'e2e-chat-response' })}\n\n`, + `data: ${safeJsonStringify({ type: 'text-end', id: 'e2e-chat-response' })}\n\n`, ), ); controller.enqueue(encoder.encode('data: [DONE]\n\n')); From 2c4dd6987dec5e3722e106881c34886a12f26c7c Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:58:15 -0600 Subject: [PATCH 76/81] =?UTF-8?q?=F0=9F=A7=AA=20test(api):=20support=20loc?= =?UTF-8?q?al=20sql=20row=20results?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api/src/db/queryRows.ts | 10 ++++++++++ packages/api/src/routes/admin/analytics/catalog.ts | 7 ++++--- packages/api/src/routes/admin/trails.ts | 7 ++++--- packages/api/src/routes/trails/index.ts | 7 ++++--- packages/api/src/services/dbMetricsService.ts | 9 +++++---- packages/api/src/services/trails.ts | 3 ++- packages/api/src/utils/DbUtils.ts | 3 ++- packages/api/src/utils/compute-pack.ts | 2 +- 8 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 packages/api/src/db/queryRows.ts diff --git a/packages/api/src/db/queryRows.ts b/packages/api/src/db/queryRows.ts new file mode 100644 index 0000000000..6b66e469bf --- /dev/null +++ b/packages/api/src/db/queryRows.ts @@ -0,0 +1,10 @@ +export type RawSqlResult = T[] | { rows?: T[] | null } | null | undefined; + +export function queryRows(result: RawSqlResult): T[] { + if (Array.isArray(result)) return result; + return result?.rows ?? []; +} + +export function firstQueryRow(result: RawSqlResult): T | undefined { + return queryRows(result)[0]; +} diff --git a/packages/api/src/routes/admin/analytics/catalog.ts b/packages/api/src/routes/admin/analytics/catalog.ts index c4eeea1274..1d149f3fea 100644 --- a/packages/api/src/routes/admin/analytics/catalog.ts +++ b/packages/api/src/routes/admin/analytics/catalog.ts @@ -1,4 +1,5 @@ import { createDb } from '@packrat/api/db'; +import { queryRows } from '@packrat/api/db/queryRows'; import { R2BucketService } from '@packrat/api/services/r2-bucket'; import { getEnv } from '@packrat/api/utils/env-validation'; import { captureApiException } from '@packrat/api/utils/sentry'; @@ -491,7 +492,7 @@ export const catalogAnalyticsRoutes = new Elysia({ prefix: '/catalog' }) ]); return { - topErrors: rows.rows.map((r) => ({ + topErrors: queryRows(rows).map((r) => ({ field: r.field, reason: r.reason, count: r.count, @@ -551,7 +552,7 @@ export const catalogAnalyticsRoutes = new Elysia({ prefix: '/catalog' }) return { jobId: params.jobId, - errorBreakdown: breakdown.rows.map((r) => ({ + errorBreakdown: queryRows(breakdown).map((r) => ({ field: r.field, reason: r.reason, count: r.count, @@ -861,7 +862,7 @@ export const catalogAnalyticsRoutes = new Elysia({ prefix: '/catalog' }) GROUP BY lpi.source, lj.last_id, lj.last_at ORDER BY lpi.source `); - const rows = (auditResult.rows ?? auditResult) as Array<{ + const rows = queryRows(auditResult) as Array<{ source: string; total_items: number; last_id: string | null; diff --git a/packages/api/src/routes/admin/trails.ts b/packages/api/src/routes/admin/trails.ts index bf059b9b7d..084fbe57af 100644 --- a/packages/api/src/routes/admin/trails.ts +++ b/packages/api/src/routes/admin/trails.ts @@ -1,4 +1,5 @@ import { createDb, createOsmDb } from '@packrat/api/db'; +import { firstQueryRow, queryRows } from '@packrat/api/db/queryRows'; import { trailConditionReports, users } from '@packrat/db/schema'; import { queryBoolean } from '@packrat/guards'; import { @@ -57,7 +58,7 @@ export const adminTrailsRoutes = new Elysia({ prefix: '/trails' }) LIMIT ${limit + 1} OFFSET ${offset} `); - const rows = z.array(RouteSearchRowSchema).parse(result.rows); + const rows = z.array(RouteSearchRowSchema).parse(queryRows(result)); const hasMore = rows.length > limit; const page = rows.slice(0, limit); @@ -140,7 +141,7 @@ export const adminTrailsRoutes = new Elysia({ prefix: '/trails' }) geojson: z.string().nullable(), }); - const row = DetailRowSchema.nullable().parse(result.rows?.[0] ?? null); + const row = DetailRowSchema.nullable().parse(firstQueryRow(result) ?? null); if (!row) return status(404, { error: 'Trail not found' }); let geometry: unknown = null; @@ -206,7 +207,7 @@ export const adminTrailsRoutes = new Elysia({ prefix: '/trails' }) WHERE osm_id = ${osmId} `); - const row = RouteSearchRowSchema.nullable().parse(result.rows?.[0] ?? null); + const row = RouteSearchRowSchema.nullable().parse(firstQueryRow(result) ?? null); if (!row) return status(404, { error: 'Trail not found' }); return { diff --git a/packages/api/src/routes/trails/index.ts b/packages/api/src/routes/trails/index.ts index 0910fe7dfd..97e0c3e318 100644 --- a/packages/api/src/routes/trails/index.ts +++ b/packages/api/src/routes/trails/index.ts @@ -1,4 +1,5 @@ import { createOsmDb } from '@packrat/api/db'; +import { firstQueryRow, queryRows } from '@packrat/api/db/queryRows'; import { authPlugin } from '@packrat/api/middleware/auth'; import { stitchRouteGeometry } from '@packrat/api/services/trails'; import { captureApiException } from '@packrat/api/utils/sentry'; @@ -74,7 +75,7 @@ export const trailsRoutes = new Elysia({ prefix: '/trails' }) LIMIT ${limit + 1} OFFSET ${offset} `); - const rows = z.array(RouteSearchRowSchema).parse(result.rows); + const rows = z.array(RouteSearchRowSchema).parse(queryRows(result)); const hasMore = rows.length > limit; const page = rows.slice(0, limit); @@ -157,7 +158,7 @@ export const trailsRoutes = new Elysia({ prefix: '/trails' }) WHERE osm_id = ${osmId} `); - const row = RouteDetailRowSchema.nullable().parse(result.rows?.[0] ?? null); + const row = RouteDetailRowSchema.nullable().parse(firstQueryRow(result) ?? null); if (!row) return status(404, { error: 'Trail not found' }); let geometry: unknown = null; @@ -233,7 +234,7 @@ export const trailsRoutes = new Elysia({ prefix: '/trails' }) WHERE osm_id = ${osmId} `); - const row = RouteSearchRowSchema.nullable().parse(result.rows?.[0] ?? null); + const row = RouteSearchRowSchema.nullable().parse(firstQueryRow(result) ?? null); if (!row) return status(404, { error: 'Trail not found' }); return { diff --git a/packages/api/src/services/dbMetricsService.ts b/packages/api/src/services/dbMetricsService.ts index a92e4875ae..5a1bda75df 100644 --- a/packages/api/src/services/dbMetricsService.ts +++ b/packages/api/src/services/dbMetricsService.ts @@ -1,4 +1,5 @@ import { createReadOnlyDb } from '@packrat/api/db'; +import { firstQueryRow, queryRows } from '@packrat/api/db/queryRows'; import { sql } from 'drizzle-orm'; /** @@ -95,7 +96,7 @@ export class DbMetricsService { current_database() AS name, pg_database_size(current_database())::text AS size_bytes `); - const row = rows.rows[0]; + const row = firstQueryRow(rows); return { name: row?.name ?? 'unknown', sizeBytes: Number(row?.size_bytes ?? 0), @@ -110,7 +111,7 @@ export class DbMetricsService { FROM pg_stat_database WHERE datname = current_database() `); - return rows.rows[0]?.stats_reset ?? null; + return firstQueryRow(rows)?.stats_reset ?? null; } private async fetchTables(): Promise { @@ -168,7 +169,7 @@ export class DbMetricsService { ORDER BY pg_total_relation_size(c.oid) DESC `); - return rows.rows.map((r) => ({ + return queryRows(rows).map((r) => ({ name: r.name, estimatedRows: Number(r.est_rows), heapBytes: Number(r.heap_bytes), @@ -213,7 +214,7 @@ export class DbMetricsService { ORDER BY pg_relation_size(s.indexrelid) DESC `); - return rows.rows.map((r) => ({ + return queryRows(rows).map((r) => ({ table: r.table, name: r.name, bytes: Number(r.bytes), diff --git a/packages/api/src/services/trails.ts b/packages/api/src/services/trails.ts index e9a6299e70..d2eea77b13 100644 --- a/packages/api/src/services/trails.ts +++ b/packages/api/src/services/trails.ts @@ -1,4 +1,5 @@ import type { createOsmDb } from '@packrat/api/db'; +import { firstQueryRow } from '@packrat/api/db/queryRows'; import type { OsmMember } from '@packrat/schemas/trails'; import { safeJsonParse } from '@packrat/utils'; import { sql } from 'drizzle-orm'; @@ -49,7 +50,7 @@ export async function stitchRouteGeometry({ const row = z .object({ geojson: z.string().nullable() }) .nullable() - .parse(result.rows?.[0] ?? null); + .parse(firstQueryRow(result) ?? null); if (!row?.geojson) return null; try { diff --git a/packages/api/src/utils/DbUtils.ts b/packages/api/src/utils/DbUtils.ts index 33d96270f6..ace8fe4c91 100644 --- a/packages/api/src/utils/DbUtils.ts +++ b/packages/api/src/utils/DbUtils.ts @@ -1,4 +1,5 @@ import { createDb } from '@packrat/api/db'; +import { queryRows } from '@packrat/api/db/queryRows'; import { catalogItems, packs } from '@packrat/db/schema'; import { and, arrayOverlaps, eq, inArray, type SQL, sql } from 'drizzle-orm'; @@ -101,7 +102,7 @@ export async function getSchemaInfo() { `; const result = await db.tag('utils.getSchemaInfo').execute(sql.raw(schemaQuery)); - return result.rows + return queryRows(result) .map((row) => `${row.table_name} (\n ${row.columns}\n );`) .join('\n\n'); } catch (error) { diff --git a/packages/api/src/utils/compute-pack.ts b/packages/api/src/utils/compute-pack.ts index 76c325c3cd..a94fc7bd36 100644 --- a/packages/api/src/utils/compute-pack.ts +++ b/packages/api/src/utils/compute-pack.ts @@ -3,7 +3,7 @@ import type { PackForWeights, PackItemForBreakdown, PackItemForWeights, -} from '@packrat/db/schema'; +} from '@packrat/db/projections'; import type { WeightUnit } from '@packrat/units'; import { displayWeight, normalize, parseWeightUnit } from '@packrat/units'; From 627f262d9802722b424dcd7b3c46d99b8d44a55b Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:57:00 -0600 Subject: [PATCH 77/81] =?UTF-8?q?=F0=9F=90=9B=20fix(swift):=20tolerate=20c?= =?UTF-8?q?atalog=20items=20without=20weights?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Catalog/CatalogView.swift | 4 +- .../Sources/PackRat/Models/Catalog.swift | 15 ++++- .../Sources/PackRat/Models/Generated.swift | 4 +- .../swift/Tests/PackRatTests/ModelTests.swift | 56 +++++++++++++++++++ 4 files changed, 72 insertions(+), 7 deletions(-) diff --git a/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift b/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift index ce7f35b52c..577f17e79e 100644 --- a/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift +++ b/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift @@ -285,8 +285,8 @@ struct AddCatalogItemToPackSheet: View { try await packsViewModel.addItem( to: packId, name: item.displayName, - weight: item.weight, - weightUnit: item.weightUnit.rawValue, + weight: item.weight ?? 0, + weightUnit: item.weightUnit?.rawValue ?? WeightUnit.g.rawValue, quantity: quantity, category: item.categories?.first, consumable: false, diff --git a/apps/swift/Sources/PackRat/Models/Catalog.swift b/apps/swift/Sources/PackRat/Models/Catalog.swift index 6445b9c54e..8eaf1c6959 100644 --- a/apps/swift/Sources/PackRat/Models/Catalog.swift +++ b/apps/swift/Sources/PackRat/Models/Catalog.swift @@ -8,7 +8,7 @@ extension CatalogItem { var displayBrand: String? { brand?.nilIfEmpty } var displayWeight: String { - guard weight > 0 else { return "" } + guard let weight, weight > 0, let weightUnit else { return "" } return String(format: "%.0f %@", weight, weightUnit.rawValue) } @@ -23,16 +23,25 @@ extension CatalogItem { // MARK: - Search response with flexible decoding // The search endpoint may return {items, page, limit, total} or a plain array. -struct CatalogSearchResponse: Codable, Sendable { +struct CatalogSearchResponse: Decodable, Sendable { let items: [CatalogItem] let total: Int? let page: Int? let limit: Int? + private enum CodingKeys: String, CodingKey { + case items + case total + case totalCount + case page + case limit + } + init(from decoder: Decoder) throws { if let container = try? decoder.container(keyedBy: CodingKeys.self) { items = (try? container.decode([CatalogItem].self, forKey: .items)) ?? [] - total = try? container.decodeIfPresent(Int.self, forKey: .total) + total = (try? container.decodeIfPresent(Int.self, forKey: .total)) + ?? (try? container.decodeIfPresent(Int.self, forKey: .totalCount)) page = try? container.decodeIfPresent(Int.self, forKey: .page) limit = try? container.decodeIfPresent(Int.self, forKey: .limit) } else if let arr = try? [CatalogItem](from: decoder) { diff --git a/apps/swift/Sources/PackRat/Models/Generated.swift b/apps/swift/Sources/PackRat/Models/Generated.swift index babdf09484..ce96a93447 100644 --- a/apps/swift/Sources/PackRat/Models/Generated.swift +++ b/apps/swift/Sources/PackRat/Models/Generated.swift @@ -168,8 +168,8 @@ struct CatalogItem: Codable, Identifiable, Sendable { let name: String let productUrl: String let sku: String - let weight: Double - let weightUnit: WeightUnit + let weight: Double? + let weightUnit: WeightUnit? let description: String? let categories: [String]? let images: [String]? diff --git a/apps/swift/Tests/PackRatTests/ModelTests.swift b/apps/swift/Tests/PackRatTests/ModelTests.swift index a0ae911c95..84b7f5518d 100644 --- a/apps/swift/Tests/PackRatTests/ModelTests.swift +++ b/apps/swift/Tests/PackRatTests/ModelTests.swift @@ -223,6 +223,62 @@ struct CatalogItemTests { #expect(inStock.isInStock == true) #expect(outOfStock.isInStock == false) } + + @Test("decodes Expo catalog rows with unknown weight") + func decodesNullableWeightFromSharedSchema() throws { + let json = """ + { + "id": 42, + "name": "Imported Gear", + "productUrl": "https://example.com/imported", + "sku": "SCRAPY-42", + "weight": null, + "weightUnit": null, + "description": null, + "categories": ["Shelter"], + "images": null, + "brand": "PackRat", + "model": null, + "ratingValue": null, + "color": null, + "size": null, + "price": null, + "availability": "in_stock", + "seller": null, + "reviewCount": null, + "createdAt": "2026-08-07T00:00:00.000Z", + "updatedAt": "2026-08-07T00:00:00.000Z" + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let item = try decoder.decode(CatalogItem.self, from: json) + + #expect(item.weight == nil) + #expect(item.weightUnit == nil) + #expect(item.displayWeight == "") + } + + @Test("catalog search response accepts shared API totalCount") + func catalogSearchResponseAcceptsTotalCount() throws { + let json = """ + { + "items": [], + "totalCount": 12, + "page": 1, + "limit": 20, + "totalPages": 1 + } + """.data(using: .utf8)! + + let response = try JSONDecoder().decode(CatalogSearchResponse.self, from: json) + + #expect(response.items.isEmpty) + #expect(response.total == 12) + #expect(response.page == 1) + #expect(response.limit == 20) + } } // MARK: - Enum decoding From 6e35ad4f7d55280d1f52887dbb609683b4e3d313 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:13:20 -0600 Subject: [PATCH 78/81] =?UTF-8?q?=F0=9F=9A=9A=20chore(swift):=20default=20?= =?UTF-8?q?to=20replacement=20app=20identity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/swift/README.md | 25 ++++++++++--------- apps/swift/docs/staging-adhoc.md | 12 ++++----- apps/swift/project.yml | 14 +++++------ .../scripts/capture-visual-screenshots.ts | 3 +-- apps/swift/scripts/watch-sync-smoke.ts | 5 ++-- apps/swift/xcconfig/Config-Debug.xcconfig | 2 +- apps/swift/xcconfig/Config-Release.xcconfig | 2 +- apps/swift/xcconfig/Config-Staging.xcconfig | 2 +- 8 files changed, 32 insertions(+), 33 deletions(-) diff --git a/apps/swift/README.md b/apps/swift/README.md index 6fb8dad7b7..c48c274b87 100644 --- a/apps/swift/README.md +++ b/apps/swift/README.md @@ -1,4 +1,4 @@ -# PackRat Swift Testing +# PackRat Native Testing The generated Xcode project is not committed. Regenerate it after changing `project.yml`: @@ -77,20 +77,21 @@ Swift E2E CI is defined in `.github/workflows/swift-e2e.yml`. See `docs/ci/swift-e2e-runner.md` for self-hosted Mac runner setup. -## TestFlight Lanes +## TestFlight Identity -The Swift iOS app has two TestFlight identities: +The Swift iOS app defaults to the replacement identity for the existing Expo/App +Store listing: -- Replacement beta: `com.andrewbierman.packrat`, display name `PackRat`. This is - the existing Expo/App Store listing and is the only lane that can validate a - seamless update for existing testers. Its embedded watchOS companion uses - `com.andrewbierman.packrat.watchkitapp`. -- Side-by-side beta: `com.andrewbierman.packrat.swift`, display name - `PackRat Swift`. This is useful for parallel Swift QA, but iOS treats it as a - separate app with separate install, keychain, and app container state. Its - embedded watchOS companion uses `com.andrewbierman.packrat.swift.watchkitapp`. +- iOS: `com.andrewbierman.packrat`, display name `PackRat` +- watchOS companion: `com.andrewbierman.packrat.watchkitapp` -Upload commands require an explicit lane so we do not accidentally test the +That is the only identity that validates a seamless update for existing testers. +The upload tooling still supports an explicit side-by-side lane for unusual +parallel QA builds, but it must be requested intentionally with `--side-by-side`; +iOS treats that as a separate app with separate install, keychain, and app +container state. + +Upload commands require an explicit lane so we do not accidentally target the wrong App Store Connect record: ```sh diff --git a/apps/swift/docs/staging-adhoc.md b/apps/swift/docs/staging-adhoc.md index 6e569d6693..04d17cfcb6 100644 --- a/apps/swift/docs/staging-adhoc.md +++ b/apps/swift/docs/staging-adhoc.md @@ -43,9 +43,9 @@ provisioning profile can install the build. |---|---| | `IOS_DIST_CERT_P12` | base64 of an Apple **Distribution** certificate + private key exported as `.p12` | | `IOS_DIST_CERT_PASSWORD` | the password you set when exporting the `.p12` | -| `IOS_ADHOC_PROVISIONING_PROFILE` | base64 of an **ad-hoc** `.mobileprovision` for `com.andrewbierman.packrat.swift`, listing QA device UDIDs | +| `IOS_ADHOC_PROVISIONING_PROFILE` | base64 of an **ad-hoc** `.mobileprovision` for `com.andrewbierman.packrat`, listing QA device UDIDs | -Team ID `666HGMV2LU` and bundle id `com.andrewbierman.packrat.swift` are hard-coded in +Team ID `666HGMV2LU` and bundle id `com.andrewbierman.packrat` are hard-coded in the workflow's export options — update them there if they change. Both the cert and the profile are created **fresh** here (an admin on the Apple @@ -75,7 +75,7 @@ fastlane run cert \ # devices → writes a .mobileprovision. fastlane run sigh \ adhoc:true \ - app_identifier:com.andrewbierman.packrat.swift \ + app_identifier:com.andrewbierman.packrat \ team_id:666HGMV2LU \ output_path:./signing ``` @@ -97,7 +97,7 @@ Portal home: **developer.apple.com/account → Certificates, Identifiers & Profiles**. Left menu has **Certificates · Identifiers · Devices · Profiles**. 1. **Devices** → **+** → register each QA device (name + UDID). -2. **Identifiers** → confirm an App ID for `com.andrewbierman.packrat.swift` +2. **Identifiers** → confirm an App ID for `com.andrewbierman.packrat` exists; if not, **+** → App IDs → App → Explicit bundle id → Register. 3. **Certificates** → **+** → **Apple Distribution** → follow the CSR steps (Keychain Access → Certificate Assistant → Request a Certificate from a CA), @@ -105,9 +105,9 @@ Profiles**. Left menu has **Certificates · Identifiers · Devices · Profiles** In **Keychain Access**, find the cert, expand it, select **both** the cert and its private key → right-click → **Export 2 items** → save as `.p12` (set a password = `IOS_DIST_CERT_PASSWORD`). -4. **Profiles** → **+** → **Ad Hoc** → App ID `com.andrewbierman.packrat.swift` +4. **Profiles** → **+** → **Ad Hoc** → App ID `com.andrewbierman.packrat` → select the distribution cert from step 3 → check every registered device → - name it `PackRat Swift Ad Hoc` → **Generate** → **Download**. + name it `PackRat Ad Hoc` → **Generate** → **Download**. Encode both: diff --git a/apps/swift/project.yml b/apps/swift/project.yml index dd70f4b049..d84c93539c 100644 --- a/apps/swift/project.yml +++ b/apps/swift/project.yml @@ -150,11 +150,11 @@ targets: CURRENT_PROJECT_VERSION: "2026072101" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU - PACKRAT_IOS_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift + PACKRAT_IOS_BUNDLE_IDENTIFIER: com.andrewbierman.packrat PRODUCT_BUNDLE_IDENTIFIER: $(PACKRAT_IOS_BUNDLE_IDENTIFIER) PACKRAT_COMPANION_BUNDLE_IDENTIFIER: $(PACKRAT_IOS_BUNDLE_IDENTIFIER) - PACKRAT_WATCH_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift.watchkitapp - PACKRAT_DISPLAY_NAME: PackRat Swift + PACKRAT_WATCH_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.watchkitapp + PACKRAT_DISPLAY_NAME: PackRat PRODUCT_MODULE_NAME: PackRat TARGETED_DEVICE_FAMILY: "1,2" ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon @@ -189,10 +189,10 @@ targets: CURRENT_PROJECT_VERSION: "2026072101" CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: 666HGMV2LU - PACKRAT_WATCH_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift.watchkitapp + PACKRAT_WATCH_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.watchkitapp PRODUCT_BUNDLE_IDENTIFIER: $(PACKRAT_WATCH_BUNDLE_IDENTIFIER) - PACKRAT_COMPANION_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.swift - PACKRAT_DISPLAY_NAME: PackRat Swift + PACKRAT_COMPANION_BUNDLE_IDENTIFIER: com.andrewbierman.packrat + PACKRAT_DISPLAY_NAME: PackRat PRODUCT_MODULE_NAME: PackRatWatch ASSETCATALOG_COMPILER_APPICON_NAME: WatchAppIcon @@ -254,7 +254,7 @@ targets: CODE_SIGN_IDENTITY: "-" CODE_SIGNING_REQUIRED: NO PRODUCT_BUNDLE_IDENTIFIER: com.andrewbierman.packrat.mac - PACKRAT_DISPLAY_NAME: PackRat Swift + PACKRAT_DISPLAY_NAME: PackRat # Match iOS target so @testable import PackRat resolves on both platforms. PRODUCT_MODULE_NAME: PackRat ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index 133b443fdd..fb25e47cca 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -81,8 +81,7 @@ const MACOS_SCHEME_PATH = resolve( SWIFT_DIR, 'PackRat.xcodeproj/xcshareddata/xcschemes/PackRat-macOS.xcscheme', ); -const WATCH_BUNDLE_ID = - nodeEnv.PACKRAT_WATCH_BUNDLE_ID ?? 'com.andrewbierman.packrat.swift.watchkitapp'; +const WATCH_BUNDLE_ID = nodeEnv.PACKRAT_WATCH_BUNDLE_ID ?? 'com.andrewbierman.packrat.watchkitapp'; const EMAIL_RE = createRegExp( oneOrMore(charIn('A-Z0-9._%+-')), '@', diff --git a/apps/swift/scripts/watch-sync-smoke.ts b/apps/swift/scripts/watch-sync-smoke.ts index 510f66ab19..309703ef20 100644 --- a/apps/swift/scripts/watch-sync-smoke.ts +++ b/apps/swift/scripts/watch-sync-smoke.ts @@ -20,9 +20,8 @@ type SimulatorPair = { const REPO_ROOT = resolve(import.meta.dir, '../../..'); const SWIFT_DIR = resolve(REPO_ROOT, 'apps/swift'); const ARTIFACT_DIR = resolve(REPO_ROOT, 'artifacts/screenshots-latest'); -const IOS_BUNDLE_ID = nodeEnv.PACKRAT_IOS_BUNDLE_ID ?? 'com.andrewbierman.packrat.swift'; -const WATCH_BUNDLE_ID = - nodeEnv.PACKRAT_WATCH_BUNDLE_ID ?? 'com.andrewbierman.packrat.swift.watchkitapp'; +const IOS_BUNDLE_ID = nodeEnv.PACKRAT_IOS_BUNDLE_ID ?? 'com.andrewbierman.packrat'; +const WATCH_BUNDLE_ID = nodeEnv.PACKRAT_WATCH_BUNDLE_ID ?? 'com.andrewbierman.packrat.watchkitapp'; const KNOWN_IOS_BUNDLE_IDS = ['com.andrewbierman.packrat', 'com.andrewbierman.packrat.swift']; const KNOWN_WATCH_BUNDLE_IDS = [ 'com.andrewbierman.packrat.watchkitapp', diff --git a/apps/swift/xcconfig/Config-Debug.xcconfig b/apps/swift/xcconfig/Config-Debug.xcconfig index 6192b15f56..cbcb9dd1bf 100644 --- a/apps/swift/xcconfig/Config-Debug.xcconfig +++ b/apps/swift/xcconfig/Config-Debug.xcconfig @@ -19,7 +19,7 @@ // echo "PACKRAT_ENV = dev" > xcconfig/Config-Debug.local.xcconfig # deployed dev // echo "PACKRAT_ENV = dev-local" > xcconfig/Config-Debug.local.xcconfig # wrangler on :8791 PACKRAT_ENV = local -PACKRAT_DISPLAY_NAME = PackRat Swift +PACKRAT_DISPLAY_NAME = PackRat // SENTRY_DSN flows xcconfig → Info.plist → runtime. // Drop the DSN into Config-Debug.local.xcconfig to enable Sentry on Debug diff --git a/apps/swift/xcconfig/Config-Release.xcconfig b/apps/swift/xcconfig/Config-Release.xcconfig index c624213796..0f22cac0d0 100644 --- a/apps/swift/xcconfig/Config-Release.xcconfig +++ b/apps/swift/xcconfig/Config-Release.xcconfig @@ -1,5 +1,5 @@ PACKRAT_ENV = production -PACKRAT_DISPLAY_NAME = PackRat Swift +PACKRAT_DISPLAY_NAME = PackRat // SENTRY_DSN flows xcconfig → Info.plist → runtime. Set this in the build // environment (CI secret, Release-build script, or Config-Release.local.xcconfig diff --git a/apps/swift/xcconfig/Config-Staging.xcconfig b/apps/swift/xcconfig/Config-Staging.xcconfig index 3ea7816811..d4d2868e98 100644 --- a/apps/swift/xcconfig/Config-Staging.xcconfig +++ b/apps/swift/xcconfig/Config-Staging.xcconfig @@ -11,7 +11,7 @@ // xcconfig files treat // as a comment, so full URLs can't be stored here. // PACKRAT_ENV selects an environment name; URLs live in APIClient.swift. PACKRAT_ENV = dev -PACKRAT_DISPLAY_NAME = PackRat Swift +PACKRAT_DISPLAY_NAME = PackRat // SENTRY_DSN flows xcconfig → Info.plist → runtime. Provided by CI from a // secret at archive time; empty disables Sentry (SDK silently no-ops). From 8c82ea66b10d12a38991d2838df0fa87c5351d01 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 8 Aug 2026 10:39:41 -0600 Subject: [PATCH 79/81] =?UTF-8?q?=F0=9F=A7=AA=20test(swift):=20harden=20la?= =?UTF-8?q?unch=20QA=20evidence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../swift/Tests/PackRatTests/ModelTests.swift | 231 ++++++++++++++++++ .../scripts/capture-visual-screenshots.ts | 7 +- apps/swift/scripts/run-e2e-macos.ts | 80 +----- apps/swift/scripts/run-e2e.ts | 100 +------- ...8-06-001-swift-launch-readiness-qa-plan.md | 15 +- 5 files changed, 249 insertions(+), 184 deletions(-) diff --git a/apps/swift/Tests/PackRatTests/ModelTests.swift b/apps/swift/Tests/PackRatTests/ModelTests.swift index 84b7f5518d..bd755887b1 100644 --- a/apps/swift/Tests/PackRatTests/ModelTests.swift +++ b/apps/swift/Tests/PackRatTests/ModelTests.swift @@ -281,6 +281,237 @@ struct CatalogItemTests { } } +// MARK: - Expo/shared API compatibility + +@Suite("Expo shared API payload compatibility") +struct ExpoSharedPayloadCompatibilityTests { + private var decoder: JSONDecoder { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + return decoder + } + + @Test("decodes representative Expo-era user data used by the Swift update") + func decodesRepresentativeExpoUserData() throws { + let payload = """ + { + "user": { + "id": "019f7b5f-e2e0-7000-9000-packrat000001", + "email": "tester@example.com", + "name": "Taylor Hiker", + "firstName": "Taylor", + "lastName": "Hiker", + "role": "USER", + "emailVerified": true, + "avatarUrl": null, + "createdAt": "2026-08-08T00:00:00.000Z", + "updatedAt": "2026-08-08T00:00:00.000Z" + }, + "pack": { + "id": "pack-expo-1", + "userId": "019f7b5f-e2e0-7000-9000-packrat000001", + "name": "Expo Weekend Pack", + "description": "Existing mobile user pack", + "category": "backpacking", + "isPublic": false, + "image": null, + "tags": ["weekend", "qa"], + "templateId": null, + "deleted": false, + "isAIGenerated": false, + "totalWeight": 2380, + "baseWeight": 1920, + "wornWeight": 300, + "consumableWeight": 160, + "createdAt": "2026-08-08T00:00:00.000Z", + "updatedAt": "2026-08-08T00:00:00.000Z", + "items": [ + { + "id": "item-expo-1", + "packId": "pack-expo-1", + "name": "Rain Shell", + "description": null, + "weight": 210, + "weightUnit": "g", + "quantity": 1, + "category": "clothing", + "consumable": false, + "worn": true, + "image": null, + "notes": "Existing Expo item", + "catalogItemId": 7001, + "userId": "019f7b5f-e2e0-7000-9000-packrat000001", + "deleted": false, + "isAIGenerated": false, + "templateItemId": null, + "createdAt": "2026-08-08T00:00:00.000Z", + "updatedAt": "2026-08-08T00:00:00.000Z" + } + ] + }, + "trip": { + "id": "trip-expo-1", + "name": "Indian Peaks Loop", + "description": "Synced from existing mobile data", + "notes": "Watch weather before Pawnee Pass", + "location": { + "latitude": 40.083, + "longitude": -105.585, + "name": "Brainard Lake Recreation Area" + }, + "startDate": "2026-09-05T00:00:00.000Z", + "endDate": "2026-09-07T00:00:00.000Z", + "userId": "019f7b5f-e2e0-7000-9000-packrat000001", + "packId": "pack-expo-1", + "deleted": false, + "createdAt": "2026-08-08T00:00:00.000Z", + "updatedAt": "2026-08-08T00:00:00.000Z" + }, + "template": { + "id": "template-expo-1", + "userId": "019f7b5f-e2e0-7000-9000-packrat000001", + "name": "Weekend Backpacking", + "description": "Saved template", + "category": "backpacking", + "image": null, + "tags": ["saved"], + "isAppTemplate": false, + "contentSource": "user", + "createdAt": "2026-08-08T00:00:00.000Z", + "updatedAt": "2026-08-08T00:00:00.000Z", + "items": [ + { + "id": "template-item-expo-1", + "packTemplateId": "template-expo-1", + "name": "Headlamp", + "weight": 85, + "weightUnit": "g", + "quantity": 1, + "category": "lighting", + "consumable": false, + "worn": false, + "notes": null + } + ] + }, + "catalog": { + "items": [ + { + "id": 9001, + "name": "Imported Catalog Item", + "productUrl": "https://example.com/catalog/imported", + "sku": "EXPO-CATALOG-1", + "weight": null, + "weightUnit": null, + "description": "ETL row without weight metadata", + "categories": ["Shelter"], + "images": null, + "brand": "PackRat", + "model": null, + "ratingValue": null, + "color": null, + "size": null, + "price": null, + "availability": "in_stock", + "seller": null, + "reviewCount": null + } + ], + "totalCount": 1, + "page": 1, + "limit": 20 + }, + "weather": { + "location": { "id": 5582371, "name": "Boulder", "region": "Colorado", "country": "United States", "lat": 40.02, "lon": -105.27 }, + "current": { + "temp_f": 72, + "temp_c": 22.2, + "feelslike_f": 72, + "feelslike_c": 22.2, + "humidity": 35, + "wind_mph": 8, + "wind_kph": 12.9, + "condition": { "text": "Sunny", "icon": null, "code": 1000 } + }, + "forecast": { "forecastday": [] }, + "alerts": { "alert": [] } + }, + "trailReport": { + "id": "trail-expo-1", + "trailName": "Pawnee Pass", + "trailRegion": "Indian Peaks Wilderness", + "surface": "mixed", + "overallCondition": "good", + "hazards": ["afternoon storms"], + "waterCrossings": 2, + "waterCrossingDifficulty": "moderate", + "notes": "Snow lingering above treeline", + "photos": [], + "userId": "019f7b5f-e2e0-7000-9000-packrat000001", + "tripId": "trip-expo-1", + "deleted": false, + "createdAt": "2026-08-08T00:00:00.000Z", + "updatedAt": "2026-08-08T00:00:00.000Z" + }, + "season": { + "suggestions": [ + { + "name": "Windy Alpine Add-ons", + "description": "Extra warmth and weather protection", + "category": "backpacking", + "tags": ["alpine"], + "items": [ + { + "name": "Warm hat", + "description": null, + "weight": 55, + "weightUnit": "g", + "quantity": 1, + "category": "clothing", + "consumable": false, + "worn": true, + "image": null, + "notes": null, + "catalogItemId": null + } + ] + } + ], + "totalInventoryItems": 1, + "location": "Boulder, CO", + "season": "fall" + } + } + """.data(using: .utf8)! + + let snapshot = try decoder.decode(ExpoPayloadSnapshot.self, from: payload) + + #expect(snapshot.user.id == snapshot.pack.userId) + #expect(snapshot.user.displayName == "Taylor Hiker") + #expect(snapshot.pack.activeItems.count == 1) + #expect(snapshot.pack.activeItems.first?.catalogItemId == 7001) + #expect(snapshot.trip.packId == snapshot.pack.id) + #expect(snapshot.trip.location?.name == "Brainard Lake Recreation Area") + #expect(snapshot.template.itemCount == 1) + #expect(snapshot.catalog.items.first?.displayWeight == "") + #expect(snapshot.catalog.total == 1) + #expect(snapshot.weather.current?.condition?.sfSymbol == "sun.max") + #expect(snapshot.trailReport.hazards == ["afternoon storms"]) + #expect(snapshot.season.suggestions.first?.items?.first?.name == "Warm hat") + } + + private struct ExpoPayloadSnapshot: Decodable { + let user: User + let pack: Pack + let trip: Trip + let template: PackTemplate + let catalog: CatalogSearchResponse + let weather: WeatherForecastResponse + let trailReport: TrailConditionReport + let season: SeasonSuggestionsResponse + } +} + // MARK: - Enum decoding @Suite("Enum graceful decoding") diff --git a/apps/swift/scripts/capture-visual-screenshots.ts b/apps/swift/scripts/capture-visual-screenshots.ts index fb25e47cca..475a8ad2c8 100644 --- a/apps/swift/scripts/capture-visual-screenshots.ts +++ b/apps/swift/scripts/capture-visual-screenshots.ts @@ -97,10 +97,7 @@ const SECRET_BUILD_SETTING_RE = createRegExp( oneOrMore(charNotIn(' \t\n\r')), [globalFlag], ); -const E2E_LOCAL_TOKEN_RE = createRegExp(exactly('e2e-local.'), oneOrMore(charIn('A-F0-9')), [ - globalFlag, - caseInsensitive, -]); +const E2E_LOCAL_TOKEN_RE = /e2e-local\.[A-Fa-f0-9]+/g; const XCODEBUILD_TIMEOUT_MS = durationFromEnv('PACKRAT_VISUAL_XCODEBUILD_TIMEOUT_MS', 30 * 60_000); const XCRESULT_EXPORT_TIMEOUT_MS = durationFromEnv('PACKRAT_XCRESULT_EXPORT_TIMEOUT_MS', 90_000); const AUTOMATION_MODE_TIMEOUT_MS = 10_000; @@ -1218,7 +1215,7 @@ async function runWatchVisualCapture(screenshotDir: string): Promise { - if (char === '&') return '&'; - if (char === '<') return '<'; - if (char === '>') return '>'; - if (char === '"') return '"'; - if (char === "'") return '''; - return char; - }).join(''); -} - function deriveLocalE2ESessionToken(): string | undefined { if (PACKRAT_ENV !== 'local' && PACKRAT_ENV !== 'dev-local') return undefined; const secret = process.env.BETTER_AUTH_SECRET ?? 'e2e-better-auth-secret-at-least-32-chars'; @@ -143,65 +132,6 @@ function deriveLocalE2ESessionToken(): string | undefined { return `e2e-local.${digest}`; } -type SchemeEnv = { - email: string; - password: string; - sessionToken?: string; - userId?: string; - allowLoginSeed: boolean; -}; - -function environmentVariableXml(key: string, value: string): string { - return [ - ' ', - ' ', - ].join('\n'); -} - -function injectScheme({ email, password, sessionToken, userId, allowLoginSeed }: SchemeEnv): void { - let content = readFileSync(SCHEME_PATH, 'utf8'); - content = removeEnvironmentVariablesBlock(content); - content = content.replace( - 'shouldUseLaunchSchemeArgsEnv = "YES"', - 'shouldUseLaunchSchemeArgsEnv = "NO"', - ); - const variables = [ - environmentVariableXml('E2E_EMAIL', email), - environmentVariableXml('E2E_PASSWORD', password), - environmentVariableXml('PACKRAT_E2E_EMAIL', uiTestEmail), - environmentVariableXml('PACKRAT_E2E_PASSWORD', uiTestPassword), - environmentVariableXml('PACKRAT_E2E_ALLOW_LOGIN_SEED', allowLoginSeed ? '1' : '0'), - ]; - if (sessionToken) - variables.push(environmentVariableXml('PACKRAT_E2E_SESSION_TOKEN', sessionToken)); - if (userId) variables.push(environmentVariableXml('PACKRAT_E2E_USER_ID', userId)); - - const block = [ - ' ', - ...variables, - ' ', - '', - ].join('\n'); - content = content.replace(' ', `${block} `); - writeFileSync(SCHEME_PATH, content); -} - -function removeEnvironmentVariablesBlock(content: string): string { - let output = content; - while (true) { - const start = output.indexOf(''); - if (start === -1) return output; - const end = output.indexOf('', start); - if (end === -1) return output; - const removalStart = output.lastIndexOf('\n', start); - const removalEnd = end + ''.length; - output = `${output.slice(0, removalStart === -1 ? start : removalStart)}${output.slice(removalEnd)}`; - } -} - function allocateResultBundle(): string { if (!existsSync(RESULTS_DIR)) mkdirSync(RESULTS_DIR, { recursive: true }); const stamp = new Date().toISOString().replaceAll(':', '-').replaceAll('.', '-'); @@ -236,14 +166,6 @@ try { throw err; } -injectScheme({ - email: E2E_EMAIL, - password: E2E_PASSWORD, - sessionToken: localE2ESessionToken, - userId: process.env.E2E_TEST_USER_ID, - allowLoginSeed, -}); -console.log('✓ Injected E2E credentials into PackRat-macOS scheme'); assertAutomationModeAvailable(); const resultBundle = allocateResultBundle(); diff --git a/apps/swift/scripts/run-e2e.ts b/apps/swift/scripts/run-e2e.ts index ce0eb0ad65..602e1a7716 100644 --- a/apps/swift/scripts/run-e2e.ts +++ b/apps/swift/scripts/run-e2e.ts @@ -13,14 +13,11 @@ import { createHash } from 'node:crypto'; * E2E_EMAIL * E2E_PASSWORD * - * How credentials reach the test runner: - * xcodebuild reads the scheme's TestAction EnvironmentVariables when - * launching XCTRunner. We inject E2E_EMAIL/E2E_PASSWORD into that block - * in the .xcscheme XML before invoking xcodebuild test. The scheme is - * regenerated from project.yml on every `bun swift`, so this edit is - * ephemeral and safe. + * Credentials reach the test runner through xcodebuild build-setting overrides, + * which populate the UITests bundle Info.plist keys declared in project.yml. + * The app receives the same values through launchEnvironment in AppUITestCase. */ -import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs'; import { resolve } from 'node:path'; import { anyOf, @@ -102,19 +99,6 @@ const allowLoginSeed = PACKRAT_ENV === 'local' || PACKRAT_ENV === 'dev-local'; const uiTestEmail = process.env.E2E_TEST_EMAIL ?? E2E_EMAIL; const uiTestPassword = process.env.E2E_TEST_PASSWORD ?? E2E_PASSWORD; -// ── Inject credentials into scheme ─────────────────────────────────────────── - -function escapeXml(s: string): string { - return Array.from(s, (char) => { - if (char === '&') return '&'; - if (char === '<') return '<'; - if (char === '>') return '>'; - if (char === '"') return '"'; - if (char === "'") return '''; - return char; - }).join(''); -} - function deriveLocalE2ESessionToken(): string | undefined { if (PACKRAT_ENV !== 'local' && PACKRAT_ENV !== 'dev-local') return undefined; const secret = process.env.BETTER_AUTH_SECRET ?? 'e2e-better-auth-secret-at-least-32-chars'; @@ -125,73 +109,6 @@ function deriveLocalE2ESessionToken(): string | undefined { return `e2e-local.${digest}`; } -type SchemeEnv = { - email: string; - password: string; - sessionToken?: string; - userId?: string; - allowLoginSeed: boolean; -}; - -function environmentVariableXml(key: string, value: string): string { - return [ - ' ', - ' ', - ].join('\n'); -} - -function injectScheme({ email, password, sessionToken, userId, allowLoginSeed }: SchemeEnv): void { - let content = readFileSync(SCHEME_PATH, 'utf8'); - - // Strip any prior EnvironmentVariables block (idempotent re-runs). - content = removeEnvironmentVariablesBlock(content); - - // Force TestAction to use its own env vars rather than inheriting from Run. - content = content.replace( - 'shouldUseLaunchSchemeArgsEnv = "YES"', - 'shouldUseLaunchSchemeArgsEnv = "NO"', - ); - - const variables = [ - environmentVariableXml('E2E_EMAIL', email), - environmentVariableXml('E2E_PASSWORD', password), - environmentVariableXml('PACKRAT_E2E_EMAIL', uiTestEmail), - environmentVariableXml('PACKRAT_E2E_PASSWORD', uiTestPassword), - environmentVariableXml('PACKRAT_E2E_ALLOW_LOGIN_SEED', allowLoginSeed ? '1' : '0'), - ]; - if (sessionToken) - variables.push(environmentVariableXml('PACKRAT_E2E_SESSION_TOKEN', sessionToken)); - if (userId) variables.push(environmentVariableXml('PACKRAT_E2E_USER_ID', userId)); - - const block = [ - ' ', - ...variables, - ' ', - '', - ].join('\n'); - - // Insert before . - content = content.replace(' ', `${block} `); - - writeFileSync(SCHEME_PATH, content); -} - -function removeEnvironmentVariablesBlock(content: string): string { - let output = content; - while (true) { - const start = output.indexOf(''); - if (start === -1) return output; - const end = output.indexOf('', start); - if (end === -1) return output; - const removalStart = output.lastIndexOf('\n', start); - const removalEnd = end + ''.length; - output = `${output.slice(0, removalStart === -1 ? start : removalStart)}${output.slice(removalEnd)}`; - } -} - // ── Pick destination ───────────────────────────────────────────────────────── function pickDestination(): string { @@ -228,15 +145,6 @@ try { // ── Run xcodebuild ─────────────────────────────────────────────────────────── -injectScheme({ - email: E2E_EMAIL, - password: E2E_PASSWORD, - sessionToken: localE2ESessionToken, - userId: process.env.E2E_TEST_USER_ID, - allowLoginSeed, -}); -console.log('✓ Injected E2E credentials into scheme'); - const dest = pickDestination(); const resultBundle = allocateResultBundle(); console.log(`→ Destination: ${dest}`); diff --git a/docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md b/docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md index 4dac80e9e5..0c33ec446e 100644 --- a/docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md +++ b/docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md @@ -12,7 +12,7 @@ execution: code ## Goal Capsule -- **Objective:** Bring the Swift iOS, iPad, macOS, and Watch surfaces to launch-readiness confidence as a seamless update path from the Expo app by syncing current branches, generating fresh screenshot evidence, running full Swift QA, and fixing any launch-blocking defects found. +- **Objective:** Bring the Swift iOS, iPad, macOS, and Watch surfaces to launch-readiness confidence as a seamless update path from the Expo app by syncing current branches, generating fresh screenshot evidence, proving Expo-to-Swift data compatibility, running full Swift QA, requesting Mikibo review, and fixing any launch-blocking defects found. - **Authority:** Expo production behavior remains the parity baseline; open Swift/mobile GitHub issues and live screenshot evidence override stale prior confidence claims; native SwiftUI/AppKit expectations govern UI polish. - **Execution profile:** Audit latest branches and PRs, preserve existing uncommitted Swift tester fixes, run screenshot and E2E coverage across Apple targets, inspect failures/screenshots manually, fix real bugs with focused tests, then commit/PR/watch CI. - **Stop conditions:** Stop only for unavailable credentials/signing infrastructure, a failing local toolchain that cannot build Apple targets, or a product decision needed to choose between Expo parity and a new Swift-native behavior. @@ -37,6 +37,7 @@ Prior Swift readiness work improved the app substantially, but repeated tester f - R1. The working branch is synced against the latest intended base branch and all relevant open Swift/TestFlight/mobile PRs and issues are reviewed before declaring readiness. - R2. Existing uncommitted Swift tester-fix work is preserved, understood, and either incorporated into this readiness branch or deliberately isolated with a clear reason. - R3. PR metadata and versioning must not contradict the app binaries, TestFlight replacement settings, or Expo baseline version. +- R3a. Mikibo review is explicitly requested on the active Swift launch PR before readiness is claimed. #### Screenshots and Manual Review @@ -49,6 +50,7 @@ Prior Swift readiness work improved the app substantially, but repeated tester f - R7. Swift iOS and macOS smoke/full or equivalent UI E2E suites run against the local deterministic API where possible. - R8. Core features are testable end to end: auth/guest entry, home navigation/search, packs/trips/items CRUD, catalog add flow, templates, weather, season suggestions, trail conditions, chat/AI fallback, settings/preferences, offline/local-first behavior, and platform-specific Watch sync smoke. - R9. Tests must verify behavior rather than hiding defects through overly broad retries, weak assertions, or fixture states that mask real deployed failures. +- R9a. Shared API/schema fixtures that were produced by, or remain compatible with, the Expo app must decode and render in Swift without losing core user data such as packs, trips, pack items, catalog items, weights, locations, weather, and AI responses. #### Launch Readiness @@ -81,6 +83,7 @@ Prior Swift readiness work improved the app substantially, but repeated tester f - AE3. Given the Swift app is intended to replace the Expo listing, when TestFlight preflight runs, then replacement bundle IDs, version/build, archive overrides, and display metadata align with that release path. - AE4. Given the local deterministic API is available, when iOS and macOS E2E flows run, then auth/guest and core CRUD/AI/weather flows are asserted with feature-specific expectations. - AE5. Given some repo-wide tickets remain outside this Swift launch scope, when the final report is written, then they are named as residuals rather than implied solved. +- AE6. Given a real Expo-era API payload or shared-schema fixture, when Swift model decoding and screenshot data-state tests run, then the same user data appears in Swift UI states without replacement-bundle migration breakage. ### Scope Boundaries @@ -136,16 +139,17 @@ Start by making branch and diff state explicit, because existing uncommitted Swi ## Implementation Units -### U1. Branch, PR, and issue audit +### U1. Branch, PR, issue, and reviewer audit - **Goal:** Establish the current launch-readiness baseline and avoid working from stale branch assumptions. -- **Requirements:** R1, R2, R3; KTD3. +- **Requirements:** R1, R2, R3, R3a; KTD3. - **Files:** `docs/plans/2026-08-06-001-swift-launch-readiness-qa-plan.md`, PR metadata, issue metadata, and any branch notes added to the PR body. - **Approach:** Inspect current branch, uncommitted diff, open PRs, recent merged Swift branches, and open Swift/mobile/TestFlight issues. Decide whether to continue on `codex/swift-beta-tester-readiness`, switch to another branch, or create a new branch from the latest base. - **Test scenarios:** 1. Covers AE1. Existing uncommitted files are listed before any edits. 2. Covers R1. Open Swift/mobile PRs and issues are summarized with scope classification. 3. Covers R3. Version/build metadata is checked against Expo and TestFlight scripts. + 4. Covers R3a. Mikibo review is requested on the active launch PR. - **Verification:** Branch state and issue scope are included in the final report and, when code changes ship, the PR description. ### U2. Swift build, script, and release metadata verification @@ -175,7 +179,7 @@ Start by making branch and diff state explicit, because existing uncommitted Swi ### U4. Full Swift E2E and core feature QA - **Goal:** Exercise launch-critical behavior beyond unit tests. -- **Requirements:** R7, R8, R9. +- **Requirements:** R7, R8, R9, R9a. - **Files:** `apps/swift/Tests/PackRatUITests`, `apps/swift/Tests/PackRatMacUITests`, `apps/swift/Tests/PackRatTests`, `apps/swift/scripts/run-e2e.ts`, `apps/swift/scripts/run-e2e-macos.ts`, and deterministic API routes under `packages/api/src`. - **Approach:** Run iOS smoke/full or targeted equivalent, iPad-capable screenshot/UITest paths, macOS smoke/full or targeted equivalent, Swift unit tests, and Watch sync smoke. Where full runs are too slow or flaky, identify the exact failing test and rerun targeted after fixes. - **Test scenarios:** @@ -183,6 +187,7 @@ Start by making branch and diff state explicit, because existing uncommitted Swi 2. Packs/trips/items CRUD and catalog add flows work with deterministic test data. 3. Weather, season suggestions, trail conditions, and chat/AI flows show data/fallback states correctly. 4. Settings/preferences and offline/local-first paths survive relaunch and network-disabled conditions. + 5. Expo/shared-schema payloads decode through Swift models and render populated data-state screenshots without falling into empty/error placeholders. - **Verification:** `bun test:swift:unit`, `bun e2e:swift:ios-smoke`, `bun e2e:swift:ios`, `bun e2e:swift:mac-smoke`, `bun e2e:swift:mac-ui`, and `bun swift:watch-sync-smoke` pass or residual blockers are filed. ### U5. Fix launch-blocking defects with regression coverage @@ -218,9 +223,11 @@ Start by making branch and diff state explicit, because existing uncommitted Swi ## Definition of Done - Latest branch, PR, and issue state has been checked and summarized. +- Mikibo review has been requested on the active Swift launch PR. - Existing uncommitted Swift tester fixes are preserved and either shipped or separated. - Fresh screenshots/contact sheets exist for supported iPhone, iPad, macOS, and Watch states. - Screenshots have been manually reviewed and any visible launch blockers are fixed or filed. +- Expo/shared-schema data compatibility is verified by tests and visible populated Swift screenshots. - Swift script, unit, iOS E2E, macOS E2E, and Watch smoke gates have passed or produced named blockers. - TestFlight replacement metadata is consistent with the intended seamless Expo update path. - Any code changes have focused regression coverage and do not weaken assertions to pass tests. From a422f12cae1487514754140610fd47a142e0c9f4 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 8 Aug 2026 16:36:31 -0600 Subject: [PATCH 80/81] =?UTF-8?q?=F0=9F=92=84=20style(swift):=20tighten=20?= =?UTF-8?q?responsive=20row=20polish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep seeded pack, trip, catalog, inventory, and home rows from clipping or crowding on compact devices while preserving the native SwiftUI list structure. --- .../Features/Catalog/CatalogView.swift | 60 +++++++++++++++---- .../GearInventory/GearInventoryView.swift | 48 ++++++++++----- .../PackRat/Features/Home/HomeView.swift | 14 ++++- .../Features/Packs/PacksListView.swift | 11 +++- .../Features/Trips/TripsListView.swift | 50 ++++++++++++---- 5 files changed, 141 insertions(+), 42 deletions(-) diff --git a/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift b/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift index 577f17e79e..38315f6bf3 100644 --- a/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift +++ b/apps/swift/Sources/PackRat/Features/Catalog/CatalogView.swift @@ -121,25 +121,21 @@ struct CatalogItemRow: View { Text(item.displayName) .font(.headline) .lineLimit(2) - HStack(spacing: 8) { - if let brand = item.displayBrand { - Text(brand).font(.caption.bold()).foregroundStyle(.tint) - } - if !item.displayWeight.isEmpty { - Label(item.displayWeight, systemImage: "scalemass") - .font(.caption).foregroundStyle(.secondary) - } - if let price = item.displayPrice { - Text(price).font(.caption.bold()).foregroundStyle(.green) - } + .fixedSize(horizontal: false, vertical: true) + ViewThatFits(in: .horizontal) { + metadataRow + metadataFlow } if let cats = item.categories, !cats.isEmpty { Text(cats.prefix(2).joined(separator: " · ")) - .font(.caption2).foregroundStyle(.tertiary) + .font(.caption2) + .foregroundStyle(.tertiary) + .lineLimit(1) } } + .layoutPriority(1) - Spacer() + Spacer(minLength: 8) VStack(alignment: .trailing, spacing: 4) { if let rating = item.ratingValue, rating > 0 { @@ -148,12 +144,15 @@ struct CatalogItemRow: View { Text(String(format: "%.1f", rating)) .font(.caption.monospacedDigit()).foregroundStyle(.secondary) } + .fixedSize(horizontal: true, vertical: false) } if !item.isInStock { Text("Out of Stock") .font(.caption2).foregroundStyle(.red) + .lineLimit(1) .padding(.horizontal, 6).padding(.vertical, 2) .background(.red.opacity(0.1), in: Capsule()) + .fixedSize(horizontal: true, vertical: false) } Button { @@ -173,6 +172,41 @@ struct CatalogItemRow: View { .padding(.horizontal, 14) .padding(.vertical, 10) } + + private var metadataRow: some View { + HStack(spacing: 8) { + metadataItems + } + .lineLimit(1) + } + + private var metadataFlow: some View { + VStack(alignment: .leading, spacing: 2) { + metadataItems + } + } + + @ViewBuilder + private var metadataItems: some View { + if let brand = item.displayBrand { + Text(brand) + .font(.caption.bold()) + .foregroundStyle(.tint) + .lineLimit(1) + } + if !item.displayWeight.isEmpty { + Label(item.displayWeight, systemImage: "scalemass") + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + } + if let price = item.displayPrice { + Text(price) + .font(.caption.bold()) + .foregroundStyle(.green) + .lineLimit(1) + } + } } struct CatalogArtwork: View { diff --git a/apps/swift/Sources/PackRat/Features/GearInventory/GearInventoryView.swift b/apps/swift/Sources/PackRat/Features/GearInventory/GearInventoryView.swift index 2839a222a4..7035c4fb79 100644 --- a/apps/swift/Sources/PackRat/Features/GearInventory/GearInventoryView.swift +++ b/apps/swift/Sources/PackRat/Features/GearInventory/GearInventoryView.swift @@ -106,7 +106,7 @@ struct GearInventoryView: View { private var inventoryList: some View { List { Section { - HStack(spacing: 16) { + HStack(spacing: 10) { statChip(value: "\(allItems.count)", label: "Items", symbol: "archivebox.fill") statChip(value: formattedWeight(totalWeight), label: "Total", symbol: "scalemass.fill") statChip(value: "\(appState.packsVM.packs.count)", label: "Packs", symbol: "backpack.fill") @@ -132,10 +132,13 @@ struct GearInventoryView: View { .foregroundStyle(Color.accentColor) Text(value) .font(.subheadline.bold()) + .lineLimit(1) + .minimumScaleFactor(0.78) } Text(label) .font(.caption2) .foregroundStyle(.secondary) + .lineLimit(1) } .frame(maxWidth: .infinity) .padding(.vertical, 8) @@ -157,35 +160,52 @@ private struct GearItemRow: View { var body: some View { VStack(alignment: .leading, spacing: 4) { - HStack { + HStack(alignment: .firstTextBaseline, spacing: 8) { Text(item.name) .font(.body) + .lineLimit(2) + .layoutPriority(1) Spacer() if item.weightInGrams > 0 { Text(formattedWeight(item.weightInGrams * Double(item.quantity))) .font(.caption.monospacedDigit()) .foregroundStyle(.secondary) + .lineLimit(1) + .fixedSize(horizontal: true, vertical: false) } } - HStack(spacing: 8) { - if let cat = item.category { - Label(cat.capitalized, systemImage: "tag") - .font(.caption2) - .foregroundStyle(.secondary) + ViewThatFits(in: .horizontal) { + HStack(spacing: 8) { + metadataItems } - Label(item.packName, systemImage: "backpack") - .font(.caption2) - .foregroundStyle(.secondary) - if item.quantity > 1 { - Text("×\(item.quantity)") - .font(.caption2.bold()) - .foregroundStyle(Color.accentColor) + VStack(alignment: .leading, spacing: 2) { + metadataItems } } } .padding(.vertical, 2) } + @ViewBuilder + private var metadataItems: some View { + if let cat = item.category { + Label(cat.capitalized, systemImage: "tag") + .font(.caption2) + .foregroundStyle(.secondary) + .lineLimit(1) + } + Label(item.packName, systemImage: "backpack") + .font(.caption2) + .foregroundStyle(.secondary) + .lineLimit(1) + if item.quantity > 1 { + Text("×\(item.quantity)") + .font(.caption2.bold()) + .foregroundStyle(Color.accentColor) + .lineLimit(1) + } + } + private func formattedWeight(_ grams: Double) -> String { if grams >= 1000 { return String(format: "%.1fkg", grams / 1000) } return String(format: "%.0fg", grams) diff --git a/apps/swift/Sources/PackRat/Features/Home/HomeView.swift b/apps/swift/Sources/PackRat/Features/Home/HomeView.swift index b760ac6a99..fdc612499f 100644 --- a/apps/swift/Sources/PackRat/Features/Home/HomeView.swift +++ b/apps/swift/Sources/PackRat/Features/Home/HomeView.swift @@ -111,10 +111,13 @@ struct HomeView: View { VStack(alignment: .leading, spacing: 4) { Text(firstName.isEmpty ? greeting : "\(greeting), \(firstName)") .font(.title2.bold()) + .lineLimit(2) + .minimumScaleFactor(0.86) .accessibilityIdentifier("home_greeting") Text("Here's your outdoor dashboard") .font(.subheadline) .foregroundStyle(.secondary) + .lineLimit(2) } Spacer(minLength: 0) @@ -141,11 +144,14 @@ struct HomeView: View { Text(summaryTitle) .font(.headline) .foregroundStyle(.primary) + .lineLimit(2) Text(summarySubtitle) .font(.subheadline) .foregroundStyle(.secondary) + .lineLimit(3) .fixedSize(horizontal: false, vertical: true) } + .layoutPriority(1) Spacer(minLength: 0) } @@ -246,6 +252,8 @@ struct HomeView: View { HStack(alignment: .firstTextBaseline, spacing: 4) { Text(value) .font(.headline.weight(.semibold)) + .lineLimit(1) + .minimumScaleFactor(0.8) Text(label) .font(.caption) .foregroundStyle(.secondary) @@ -404,6 +412,8 @@ private struct SummaryActionButton: View { HStack(spacing: 6) { Image(systemName: symbol) Text(title) + .lineLimit(1) + .minimumScaleFactor(0.82) } .font(.subheadline.weight(.semibold)) .frame(maxWidth: .infinity) @@ -461,11 +471,13 @@ private struct HomeActionRow: View { Text(action.title) .font(.body) .foregroundStyle(.primary) + .lineLimit(1) Text(action.subtitle) .font(.caption) .foregroundStyle(.secondary) - .lineLimit(1) + .lineLimit(2) } + .layoutPriority(1) Spacer(minLength: 8) diff --git a/apps/swift/Sources/PackRat/Features/Packs/PacksListView.swift b/apps/swift/Sources/PackRat/Features/Packs/PacksListView.swift index 0e94292a42..2205cc5cfc 100644 --- a/apps/swift/Sources/PackRat/Features/Packs/PacksListView.swift +++ b/apps/swift/Sources/PackRat/Features/Packs/PacksListView.swift @@ -195,8 +195,11 @@ private struct PackRowView: View { var body: some View { VStack(alignment: .leading, spacing: 4) { - HStack { - Text(pack.name).font(.headline) + HStack(alignment: .firstTextBaseline, spacing: 8) { + Text(pack.name) + .font(.headline) + .lineLimit(2) + .layoutPriority(1) Spacer() if let total = pack.totalWeight, total > 0 { Text(pack.formattedWeight(total)) @@ -205,6 +208,7 @@ private struct PackRowView: View { .padding(.horizontal, 7) .padding(.vertical, 2) .background(.fill.tertiary, in: Capsule()) + .fixedSize(horizontal: true, vertical: false) } } HStack(spacing: 8) { @@ -212,14 +216,17 @@ private struct PackRowView: View { Label(cat.label, systemImage: cat.symbol) .font(.caption) .foregroundStyle(.secondary) + .lineLimit(1) } Text("\(pack.itemCount) item\(pack.itemCount == 1 ? "" : "s")") .font(.caption) .foregroundStyle(.secondary) + .lineLimit(1) if pack.isPublic == true { Image(systemName: "globe").font(.caption2).foregroundStyle(.tint) } } + .lineLimit(1) } .padding(.vertical, 2) } diff --git a/apps/swift/Sources/PackRat/Features/Trips/TripsListView.swift b/apps/swift/Sources/PackRat/Features/Trips/TripsListView.swift index 8cc9593c87..0f06762fe9 100644 --- a/apps/swift/Sources/PackRat/Features/Trips/TripsListView.swift +++ b/apps/swift/Sources/PackRat/Features/Trips/TripsListView.swift @@ -128,26 +128,52 @@ struct TripsListView: View { } private struct TripRowView: View { + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + let trip: Trip var body: some View { VStack(alignment: .leading, spacing: 4) { - Text(trip.name).font(.headline) - HStack(spacing: 10) { - if let loc = trip.location?.name { - Label(loc, systemImage: "mappin") - .font(.caption).foregroundStyle(.secondary) - } - if !trip.dateRange.isEmpty { - Label(trip.dateRange, systemImage: "calendar") - .font(.caption).foregroundStyle(.secondary) + Text(trip.name) + .font(.headline) + .lineLimit(2) + .fixedSize(horizontal: false, vertical: true) + if horizontalSizeClass == .compact { + VStack(alignment: .leading, spacing: 3) { + metadataItems } - if trip.packId != nil { - Label("Pack linked", systemImage: "backpack") - .font(.caption).foregroundStyle(.secondary) + .lineLimit(1) + } else { + ViewThatFits(in: .horizontal) { + HStack(spacing: 10) { + metadataItems + } + VStack(alignment: .leading, spacing: 3) { + metadataItems + } } + .lineLimit(1) } } .padding(.vertical, 2) } + + @ViewBuilder + private var metadataItems: some View { + if let loc = trip.location?.name { + Label(loc, systemImage: "mappin") + .font(.caption) + .foregroundStyle(.secondary) + } + if !trip.dateRange.isEmpty { + Label(trip.dateRange, systemImage: "calendar") + .font(.caption) + .foregroundStyle(.secondary) + } + if trip.packId != nil { + Label("Pack linked", systemImage: "backpack") + .font(.caption) + .foregroundStyle(.secondary) + } + } } From 2a8a325666be8eaea0adf70bc8efb243cab8f1b3 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Sat, 8 Aug 2026 17:31:47 -0600 Subject: [PATCH 81/81] =?UTF-8?q?=F0=9F=92=84=20style(watch):=20tighten=20?= =?UTF-8?q?trail=20report=20screenshots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PackRatWatch/PackRatWatchApp.swift | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/swift/Sources/PackRatWatch/PackRatWatchApp.swift b/apps/swift/Sources/PackRatWatch/PackRatWatchApp.swift index c93a27f957..81f60e47a7 100644 --- a/apps/swift/Sources/PackRatWatch/PackRatWatchApp.swift +++ b/apps/swift/Sources/PackRatWatch/PackRatWatchApp.swift @@ -183,18 +183,25 @@ private struct WatchTrailReportView: View { NavigationStack { List { Section { - VStack(alignment: .leading, spacing: 10) { - Label(trail.title, systemImage: "figure.hiking") - .font(.headline) - .lineLimit(2) - if connectivity.lastDraft != nil { - Label("Draft queued", systemImage: "checkmark.circle.fill") - .font(.caption) - .foregroundStyle(.green) + VStack(alignment: .leading, spacing: 6) { + HStack(alignment: .firstTextBaseline, spacing: 6) { + Label(trail.title, systemImage: "figure.hiking") + .font(.subheadline.weight(.semibold)) + .lineLimit(2) + .minimumScaleFactor(0.85) + Spacer(minLength: 4) + if connectivity.lastDraft != nil { + Image(systemName: "checkmark.circle.fill") + .font(.caption) + .accessibilityLabel("Draft queued") + } } + .foregroundStyle(connectivity.lastDraft == nil ? Color.primary : Color.green) + WatchMetricRow(title: "Condition", value: trail.conditionText, symbol: "leaf") WatchMetricRow(title: "Hazards", value: "\(trail.hazardCount)", symbol: "exclamationmark.triangle") } + .padding(.vertical, 2) } Section("Condition") { @@ -243,10 +250,13 @@ private struct WatchMetricRow: View { .foregroundStyle(.tint) .frame(width: 18) Text(title) + .lineLimit(1) + .minimumScaleFactor(0.85) Spacer() Text(value) .foregroundStyle(.secondary) .lineLimit(1) + .minimumScaleFactor(0.8) } .font(.caption) }