From 68511b712ad2f22b861a48dfb3131ca39eaf7e52 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Wed, 30 Sep 2026 19:38:16 +0700 Subject: [PATCH] feat(sidebar): sync table folders between Macs now that their CloudKit schema is in Production --- CHANGELOG.md | 1 + CloudKit/production-schema.ckdb | 40 +++++++++++++++++++ .../SyncSchemaFields.swift | 13 +++--- .../SyncSchemaRegistry.swift | 2 +- .../SyncSchemaGateTests.swift | 2 +- docs/features/icloud-sync.mdx | 2 +- 6 files changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55c3c395e5..3c038b7ed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SAP HANA database driver plugin. (#1966) - Folders for tables and views in the sidebar. (#3167) +- iCloud sync for table folders between Macs. - Shift JIS, EUC-JP, GB 18030, Big5, EUC-KR and UTF-16 options for CSV and SQL import. ### Changed diff --git a/CloudKit/production-schema.ckdb b/CloudKit/production-schema.ckdb index ef094248ff..87e6fba258 100644 --- a/CloudKit/production-schema.ckdb +++ b/CloudKit/production-schema.ckdb @@ -244,6 +244,46 @@ DEFINE SCHEMA GRANT READ TO "_world" ); + RECORD TYPE TableFolder ( + "___createTime" TIMESTAMP, + "___createdBy" REFERENCE, + "___etag" STRING, + "___modTime" TIMESTAMP, + "___modifiedBy" REFERENCE, + "___recordID" REFERENCE, + connectionId STRING QUERYABLE SEARCHABLE SORTABLE, + createdAt TIMESTAMP QUERYABLE SORTABLE, + database STRING QUERYABLE SEARCHABLE SORTABLE, + folderId STRING QUERYABLE SEARCHABLE SORTABLE, + modifiedAtLocal TIMESTAMP QUERYABLE SORTABLE, + name STRING QUERYABLE SEARCHABLE SORTABLE, + schema STRING QUERYABLE SEARCHABLE SORTABLE, + schemaVersion INT64 QUERYABLE SORTABLE, + updatedAt TIMESTAMP QUERYABLE SORTABLE, + GRANT WRITE TO "_creator", + GRANT CREATE TO "_icloud", + GRANT READ TO "_world" + ); + + RECORD TYPE TableFolderItem ( + "___createTime" TIMESTAMP, + "___createdBy" REFERENCE, + "___etag" STRING, + "___modTime" TIMESTAMP, + "___modifiedBy" REFERENCE, + "___recordID" REFERENCE, + connectionId STRING QUERYABLE SEARCHABLE SORTABLE, + database STRING QUERYABLE SEARCHABLE SORTABLE, + folderId STRING QUERYABLE SEARCHABLE SORTABLE, + modifiedAtLocal TIMESTAMP QUERYABLE SORTABLE, + name STRING QUERYABLE SEARCHABLE SORTABLE, + schema STRING QUERYABLE SEARCHABLE SORTABLE, + schemaVersion INT64 QUERYABLE SORTABLE, + GRANT WRITE TO "_creator", + GRANT CREATE TO "_icloud", + GRANT READ TO "_world" + ); + RECORD TYPE Users ( "___createTime" TIMESTAMP, "___createdBy" REFERENCE, diff --git a/Packages/TableProCore/Sources/TableProSyncTransport/SyncSchemaFields.swift b/Packages/TableProCore/Sources/TableProSyncTransport/SyncSchemaFields.swift index caf8700967..4373916db5 100644 --- a/Packages/TableProCore/Sources/TableProSyncTransport/SyncSchemaFields.swift +++ b/Packages/TableProCore/Sources/TableProSyncTransport/SyncSchemaFields.swift @@ -103,10 +103,6 @@ public enum SQLFavoriteFolderSyncField: String, SyncSchemaField { ] } -/// Empty until `TableFolder` is deployed to Production, for the reason `FavoriteDatabaseSyncField` -/// once gave: the gated subscript drops every write, so the type stays inert rather than rejected. -/// Flip this set and `SyncRecordType.verifiedInProduction` in the commit that carries the refreshed -/// `production-schema.ckdb`. public enum TableFolderSyncField: String, SyncSchemaField { case folderId case connectionId @@ -118,7 +114,10 @@ public enum TableFolderSyncField: String, SyncSchemaField { case modifiedAtLocal case schemaVersion - public static let verifiedInProduction: Set = [] + public static let verifiedInProduction: Set = [ + .folderId, .connectionId, .database, .schema, .name, + .createdAt, .updatedAt, .modifiedAtLocal, .schemaVersion + ] } /// One object filed in a folder. The record is named after the object, not the folder, so moving a @@ -132,7 +131,9 @@ public enum TableFolderItemSyncField: String, SyncSchemaField { case modifiedAtLocal case schemaVersion - public static let verifiedInProduction: Set = [] + public static let verifiedInProduction: Set = [ + .connectionId, .database, .schema, .name, .folderId, .modifiedAtLocal, .schemaVersion + ] } /// Everything except the password, which stays in the Keychain, and except the payload of a diff --git a/Packages/TableProCore/Sources/TableProSyncTransport/SyncSchemaRegistry.swift b/Packages/TableProCore/Sources/TableProSyncTransport/SyncSchemaRegistry.swift index 39d198c7fd..96bdcc32d8 100644 --- a/Packages/TableProCore/Sources/TableProSyncTransport/SyncSchemaRegistry.swift +++ b/Packages/TableProCore/Sources/TableProSyncTransport/SyncSchemaRegistry.swift @@ -5,7 +5,7 @@ public extension SyncRecordType { static let verifiedInProduction: Set = [ .connection, .group, .tag, .settings, .favorite, .favoriteFolder, .tableFavorite, .sshProfile, .credentialProfile, - .favoriteDatabase + .favoriteDatabase, .tableFolder, .tableFolderItem ] var productionSchemaState: ProductionSchemaState { diff --git a/Packages/TableProCore/Tests/TableProSyncTests/SyncSchemaGateTests.swift b/Packages/TableProCore/Tests/TableProSyncTests/SyncSchemaGateTests.swift index bb0ba6e0ed..7db03d70c7 100644 --- a/Packages/TableProCore/Tests/TableProSyncTests/SyncSchemaGateTests.swift +++ b/Packages/TableProCore/Tests/TableProSyncTests/SyncSchemaGateTests.swift @@ -60,7 +60,7 @@ struct SyncSchemaGateTests { /// refreshed `production-schema.ckdb`. Anything gated and unlisted is a type that will never /// sync, which is what this test exists to catch; anything listed and no longer gated means the /// deploy landed and the entry is stale. Comparing sets catches both. - private static let pendingProductionDeploy: Set = [.tableFolder, .tableFolderItem] + private static let pendingProductionDeploy: Set = [] @Test("Every record type the app declares is deployed, or is explicitly awaiting deployment") func allRecordTypesAreWritable() { diff --git a/docs/features/icloud-sync.mdx b/docs/features/icloud-sync.mdx index 375cc25cfd..9dc6bd31b3 100644 --- a/docs/features/icloud-sync.mdx +++ b/docs/features/icloud-sync.mdx @@ -22,7 +22,7 @@ Each synced category has its own toggle under **Sync Categories**. | **SSH Profiles** | Yes | Named [SSH profiles](/connections/ssh-profiles) | | **Credential Profiles** | Not yet | Named [credential profiles](/connections/credential-profiles) stay on the Mac that created them until their CloudKit schema ships | | **Table Favorites** | Yes | The names shown in the Favorites tab and pinned in table lists | -| **Table Folders** | Not yet | The sidebar's [folders](/features/connection-window#folders) stay on the Mac that created them until their CloudKit schema ships | +| **Table Folders** | Yes | Between Macs only. The sidebar's [folders](/features/connection-window#folders) and the tables and views filed in them | | **Database Favorites** | Yes | Favorited databases and their Development / Testing / Production tags | | **Saved Queries** | Yes | Saved SQL and their folders | | **Settings** | Yes | Between Macs only. General, Appearance, Editor, Data Grid, History, Tabs, Keyboard, and AI settings, plus custom AI slash commands and saved per-table column widths and order |