Skip to content

fix(ios): pre-release fixes for editing, sync, SSH keys and the connection screen - #2986

Merged
datlechin merged 14 commits into
mainfrom
fix/ios-connection-data-integrity
Sep 18, 2026
Merged

datlechin merged 14 commits into
mainfrom
fix/ios-connection-data-integrity

Conversation

@datlechin

Copy link
Copy Markdown
Member

Summary

Pre-release fixes for the iPhone and iPad app, from the collateral review of #2980. They cover four areas: editing and sync, SSH keys, the connection screen and forms, and data safety. Each area was designed and reviewed on its own branch, then integrated here and reviewed as one diff.

Root causes and fixes

Edits, sync and local files

Defect Cause Fix
An edit undid changes that synced in while the form was open, and dropped SSH jump hosts set on the Mac The form wrote back the whole record it loaded The form tracks what changed since it opened (ConnectionFormEdits, LibraryFormEdits) and applies only that to the current record. AppState.mutateConnection, mutateGroup and mutateTag do a read-modify-write and return a LibraryWriteOutcome. SSH tunnel and Oracle options merge field by field
An edit closed as if saved after the record changed or was deleted on another device The save ignored whether the write landed missing, refused and invalidPlacement show an alert, and a missing save deletes the secrets it just wrote
iCloud sync failed after signing in to a different Apple Account The change token, record cache and tombstones belonged to the old account adoptAccount runs before every pull. A switch clears the token, cache, tombstones and last sync date, and keeps local edits. An install that synced before the account id was recorded starts over once. A deletion the server answers with unknownItem stops retrying
SQLite and DuckDB connections lost their file after a restore, or opened an empty database Absolute container paths change on restore, and the drivers created a missing file on open The container path is re-rooted at resolve time, for container ids this install recorded. Stored and synced paths stay absolute. Drivers never create a database on open, so a missing file shows Database File Unavailable. New databases are created on Save only

SSH keys

Defect Cause Fix
Pasted or picked private keys saved in plain text in connections.json SSHConfiguration.privateKeyData was encoded with the record Keys live only in the Keychain. PastedSSHKeyMigration moves existing keys on first load without marking anything dirty, and a Keychain refusal leaves the file byte for byte unchanged
Test Connection stored its credentials in the Keychain, synced with Sync Passwords on The test used the app's own secure store Test Connection runs on a throwaway ConnectionManager over an in-memory EphemeralSecureStore
Picking an SSH key file replaced another connection's key file of the same name The picker copied to a fixed name It copies under a unique name, records the path only after the copy succeeds, and uses a file already in Documents where it is

Connection screen and forms

  • Page and row arrows moved into one bottom safe-area bar, above the tab bar on iOS 26 and later.
  • Passphrase encrypt and decrypt run off the main actor.
  • Cancel asks before discarding, and swipe-to-dismiss is refused, on the connection, group, tag, insert-row and row-edit forms. An untouched form with a leftover stored key no longer counts as changed, and a partial save moves the baseline to what was saved.
  • A synced edit updates the open screen in place, and only a change to how it connects rebuilds it. A deleted connection keeps its screen up behind Connection Deleted.
  • Every Spotlight publish replaces the whole domain, so deleted connections leave Spotlight, Siri, Shortcuts and Handoff.
  • Deleting a tag asks first and names how many connections use it.
  • Confirming a row insert re-checks safe mode, through the same ConfirmedWriteGate as row edits.

Localization

17 strings (the save failure titles and messages, Database File Unavailable and every local file error) translated into Vietnamese, Korean, Simplified and Traditional Chinese. localization.py --target ios verify reports 729 of 729 in all four languages.

Deliberately not fixed here

The last review round found defects in three changes that round had added. Each round kept producing new ones, so those three were cut back to main's behaviour instead of patched again:

  • Unsaved edits in one iPad window do not hold a redial started from another. An app-wide hold registry was tried and reverted (527b059dd). It leaked a hold when a window closed with an editor open, which queued every later rebuild in every window until relaunch. It also held rebuilds for every connection, not just the edited one. The right shape is a hold keyed by connection, released on scene disconnect, and it needs its own design.
  • The Keychain sweep still spares pasted keys of deleted connections. Sweeping device-local keys deletes the only copy of a key when the app is reinstalled and the connections have not synced back yet.
  • Import Replace keeps the replaced connection's pasted key. A stored key takes precedence over a key path at runtime, so deleting it on Replace can remove the only working credential. Keeping an unused key costs nothing.

Also out of scope: macOS never detects an iCloud account switch, because checkAccountStatus writes lastAccountId before the observer compares. That needs a separate macOS change.

Verification

  • Full iOS test target on the iPhone 18 Pro simulator (iOS 27): 666 passed, 0 failed, 27 skipped (the live-server tests).
  • swift test --package-path Packages/TableProCore --filter "TableProSyncTests|TableProImportTests|TableProModelsTests": all pass.
  • SwiftLint strict on every touched Swift file: clean apart from the two rules the iOS target exempts (public_error_text_in_log, storage_environment_*).
  • Both docs checks pass. check-ios-shared-isolation.py passes on 21 files.

Not verified: the iOS 18 to 25 layout paths (only the iOS 27 runtime is installed here), live CloudKit, a real account switch, and a device restore.

…t only the one that sees it"

This reverts commit bb966741d95d31135ffc1a58c4b80f63d28f0a33.
@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, 2:50 PM

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

@datlechin
datlechin merged commit 5cf93b2 into main Sep 18, 2026
10 of 13 checks passed
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