Skip to content

feat(ios): rebuild the first run and connection list for the App Store release - #2980

Merged
datlechin merged 3 commits into
mainfrom
feat/ios-first-run-revamp
Sep 18, 2026
Merged

datlechin merged 3 commits into
mainfrom
feat/ios-first-run-revamp

Conversation

@datlechin

Copy link
Copy Markdown
Member

Rebuilds the iPhone and iPad first run and connection list, and clears the App Store blockers the audit found before the first submission.

Why the old shape could not be patched

The first run was a root swap to a two-page tour, and the state behind it could not say what a native first run has to ask:

Defect Where Fixed by
Turning iCloud Sync off did not stop uploads: sync() never read the setting, and every edit scheduled a sync IOSSyncCoordinator.sync, scheduleSyncAfterChange The coordinator owns the gate (isEnabled), with the Mac's decide/settle generation guard so a running sync cannot overwrite "off"
iCloud sync and usage analytics defaulted to on, and the heartbeat sent 10 s after first launch, before any consent (Guideline 5.1.1(ii)) AppPreferences, IOSAnalyticsProvider "Not chosen" is its own state (OnboardingPreferences); the heartbeat starts only after Share Usage Data
A library that failed to load read as empty, and the next add overwrote the unreadable file AppState.persist Every write refuses while the load failed; the list shows Connections Unavailable with Try Again; widget and Spotlight are not wiped
The app lock was a ZStack sibling, so sheets, the open connection and the app switcher snapshot sat above it TableProMobileApp A per-scene UIWindow at .alert + 1 through a SwiftUI scene delegate, with a single-flight unlock shared by every scene
Deep links and .tablepro files were app-wide state that any iPad window could consume, and an import replaced a half-filled form AppState.pending* ScenePresenter per scene: latest-wins intent, held while a sheet, the lock or the first run is up
No device-local connection, so a sample database would have synced to the Mac TableProModels.DatabaseConnection isSample, read through one participatesInSync predicate by push, dirty marks, tombstones, export and Handoff

What changed

  • First run: one sheet over the list (form sized on iPad), Welcome, then Sync with iCloud only when an iCloud account is available, then Share Usage Data? listing exactly what the report carries. Swiping it away declines what was not answered. TestFlight users who finished the old tour keep syncing and are asked about usage data once.
  • Sync off means nothing leaves the device. Edits made meanwhile stay queued and go up with the next ordinary sync when it is turned back on, so deletions on either side survive the round trip.
  • Sample database: the Mac's Chinook, bundled, opened from the empty list or the More menu at the Track table, reset from its row.
  • What's New: typed, localized highlights per version, shown once after an update and always under Settings > About.
  • TipKit: three inline tips, held back until the first run is done and the list has been used.
  • List: one state model (loading, failed, checking iCloud, iCloud unavailable, empty, content); group rows swipe to edit and delete and show even with no connections; pull to refresh only with sync on; edit mode ends when the list empties; rename is keyed by row; rows reflow at accessibility sizes; missing engine icons fall back to an SF Symbol.
  • Settings: iCloud, Privacy, Live Activities sections; turning the lock off asks for authentication; About has What's New, the privacy policy link and Acknowledgements.
  • App Store: PrivacyInfo.xcprivacy (UserDefaults CA92.1, FileTimestamp C617.1 and 3B52.1, DiskSpace E174.1; Device ID and Product Interaction for Analytics, not linked, no tracking), and processing, NSAllowsArbitraryLoads and NSBonjourServices removed from Info.plist with evidence that nothing uses them.
  • Acknowledgements: licenses.yml gains platforms; a committed iOS projection (scripts/generate-ios-acknowledgements.py) is checked against it by ThirdPartyLicenseInventoryTests, which now also scans the iOS version pins. Chinook is attributed (MIT).
  • Removed: the toolbar sync button, Settings Refresh from iCloud and Sync Now, Manage Groups, the Recent Clear button, the More menu tag filter (search tokens remain), the old tour, and verified dead code.

Before / After

First run. Left: the old two-page tour. Middle and right: the new welcome sheet in light and dark.

Old onboarding page beside the new welcome sheet in light and dark

Connection list. Left: before, with the leading iCloud button. Middle and right: after, with Settings alone on the leading edge.

Old connection list beside the new one in light and dark

New screens (no before): the usage data question, the empty list with the sample database, and Settings top and bottom.

Share Usage Data page, empty connection list, and two Settings screens

iPad: the welcome sheet as a form sheet over the library.

Welcome sheet centered over the connection list on iPad

Verification

  • iOS target through the Xcode MCP on the iPhone 18 Pro simulator (iOS 27): 475 tests, 448 passed, 0 failed, 27 skipped (live database driver tests). 52 new tests across FirstRunPlanTests, OnboardingPreferencesTests, SceneIntentTests, ScenePresenterTests, ConnectionListStateTests, SampleDatabaseInstallerTests, AppStateLibraryTests, IOSSyncCoordinatorTests, AcknowledgementsInventoryTests.
  • Package: DatabaseConnectionSampleTests 3/3, SyncRecordCache and metadata suites 18/18. macOS: verify.sh build PASS, ThirdPartyLicenseInventoryTests 11/11.
  • Driven in the simulator: fresh first run, skip paths, sample opening straight into Track, the sample's menu, Settings, Acknowledgements, What's New; lock with Require Face ID over an open New Connection form (form hidden and untappable, typed name kept after unlock, app switcher shows a locked card); tablepro://connect/<id>/table/Album opening the table, a link held while Settings is open and delivered on close, an unknown id ignored; iPad form sheet.
  • Review: Codex was at its usage limit (resets 2026-09-22), so the diff was read by the code-review skill (4 findings, all fixed: re-enable during a running sync, deletions resurrected by a token reset, the sample editable from its Info tab, "Checking iCloud" skipped on re-enable) and a security review (no findings).
  • swiftlint --strict on every touched Swift file, docs/scripts/check-writing-style.sh, check-docs-against-source.py.

Not automated

The iOS app has no UI test target; adding one is its own change. The flows above were driven by hand in the simulator instead. A trackpad or hardware keyboard on iPad and VoiceOver were not exercised.

For you to decide before submission

  • ITSAppUsesNonExemptEncryption stays NO, but the app ships its own OpenSSL, libssh2 and BoringSSL; answer App Store Connect's export questions for this binary.
  • MariaDB Connector/C and FreeTDS are LGPL and statically linked into the App Store binary.
  • https://tablepro.app/privacy has to exist and cover the iOS heartbeat.
  • The new strings are extracted into the iOS catalog untranslated for ko, vi, zh-Hans and zh-Hant.

@mintlify

mintlify Bot commented Sep 18, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
TablePro 🟢 Ready View Preview Sep 18, 2026, 11:01 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant