Skip to content

Commit 4fad0b8

Browse files
J2TeamNNLclaudedatlechin
authored
feat(sidebar): add favorite tables with iCloud sync and sidebar improvements (#1422)
* Update .gitignore * Update CLAUDE.md * Update .gitignore * feat(sidebar): add favorite tables * feat(sidebar): add recent tables, star toggle, create-table button, overflow fix - Recent tables section at top of Tables sidebar (last 10 per connection/database, in-memory, clears on quit). RecentTablesStore pushes on every openTableTab call. - TableRow trailing star button toggles favorite inline; overlay star badge removed. Add/Remove Favorites dropped from SidebarContextMenu (star button replaces it). - Favorites tab now has only Tables and Queries sections (Recent removed). - Plus button next to sidebar search field opens Create Table tab; disabled in safe mode. - Window minimum width now recomputes dynamically when sidebar or inspector collapse/expand, preventing layout overflow on small windows. * feat(sidebar): handle tableFavorite in conflict resolution, fix showERDiagram call * Update .gitignore * refactor(sidebar): remove ER diagram context menu item, drop SidebarTableOrdering * fix(sidebar): address PR review blockers and design concerns - feat(sync): add syncTableFavorites toggle to SyncSettings and SyncSection - refactor(sidebar): scope FavoriteTablesStorage by (connectionId, schema, name) instead of global table name - fix(sync): gate tableFavorite push/apply/clear behind syncTableFavorites setting - fix(storage): add NSLock to FavoriteTablesStorage for thread safety - refactor(storage): change RecentTablesStore.Key.database to String? with nilIfEmpty convention - fix(changelog): add View ER Diagram removal entry - test: update FavoriteTablesStorageTests for connection-scoped favorites - test: update SyncRecordMapperFavoriteTableTests with connectionId and schema - test: add nil-database test to RecentTablesStoreTests - test(ui): add window minimum size assertion * fix(sidebar): address review — node-id schema, list selection, lock isolation, cleanups * fix(sidebar): scope table favorites by database * fix(sidebar): show create-table button only on the Tables tab * fix(launch): skip live iCloud sync under TABLEPRO_UI_TESTING * refactor(sidebar): drop unused RecentTablesStore lastAccessedAt * fix(sidebar): use system colors in TableRowLogic to match color tests * refactor(sidebar): move create-table action into the sidebar bottom bar * refactor(sidebar): reveal favorite star on hover and refine favorites/recents UX * fix(sidebar): drop hardcoded footer divider, use native bottom bar (separator on scroll) * fix(sidebar): footer inherits sidebar vibrancy instead of opaque material * fix(sidebar): use hard scroll-edge style so footer divider appears on overflow * refactor(sidebar): use a static footer divider, drop scroll-edge experiment * refactor(sidebar): remove the Recent tables section * fix(sidebar): drop duplicate context menu and add accessibility label on favorite table rows * refactor(sidebar): type-safe favorite selection and drop AnyView from the favorites tree * fix(test): use explicit self for connectionStorage in GroupStorageTests setUp * test(sidebar): cover FavoriteSelection round-trip and scope table selection by database * docs(claude-md): correct favorite tables storage scope * fix(sidebar): native styling for Tables footer schema switcher (#1422) --------- Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Ngô Quốc Đạt <datlechin@gmail.com>
1 parent e1d88fb commit 4fad0b8

37 files changed

Lines changed: 1361 additions & 382 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ Thumbs.db
125125
*.p12
126126
*.mobileprovision
127127
Secrets.xcconfig
128+
Local.xcconfig
128129

129130
# Debug
130131
*.log
@@ -154,3 +155,4 @@ fix-1322-plugin-abi-and-registry-overhaul.diff
154155

155156
# Issue analysis blueprints (local only)
156157
.analysis/
158+
.docs/

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Mark a table as a favorite by clicking the star button at the end of its sidebar row. Favorites are scoped to the connection, database, and schema, pinned to the top of their section, appear in a dedicated Tables group in the Favorites tab, and sync through iCloud when the Table Favorites toggle is on.
13+
- A plus button in the bottom bar of the Tables sidebar opens a menu to create a new table or view, without right-clicking. It's disabled while safe mode blocks writes.
1214
- The sidebar can show every database on the server as an expandable tree. Switch a connection between the flat list and the tree from the View menu (Sidebar Layout); right-click a database or schema to set it active. Set the default layout for new connections in Settings, General. Applies to MySQL, MariaDB, PostgreSQL, MSSQL, ClickHouse, Redshift; SQLite, Redis, MongoDB, BigQuery keep their existing sidebar. (#139)
1315
- A connection can read its password from a file, environment variable, or command at connect time instead of the Keychain, so scripts can provision a connection without entering the password by hand. (#1254)
1416

17+
### Changed
18+
19+
- The Tables sidebar bottom bar uses native macOS styling. The schema switcher is a borderless pull-down menu on the sidebar's own background instead of a wide gray bordered control, matching the Favorites footer, and switching schemas now goes through the same path as the toolbar so filters and the active tab stay in sync.
20+
- The Maintenance submenu in the sidebar context menu is hidden when no maintenance operations are available or the target is read-only, instead of showing an empty disabled menu.
21+
- The window minimum width now adjusts to the visible panes, so opening the inspector on a small window no longer pushes content off-screen.
22+
23+
### Removed
24+
25+
- "Create New Table…" from the sidebar right-click menu. Use the plus button in the Tables sidebar footer instead.
26+
1527
### Fixed
1628

1729
- Moving a connection into or out of a group now syncs across devices, instead of leaving it ungrouped on your other Macs.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ Missing a case produces a wrong "{Language} Query" title on the first frame.
168168
| Tab state | JSON persistence | `TabPersistenceService` / `TabStateStorage` |
169169
| Filter presets | UserDefaults | `FilterSettingsStorage` |
170170
| Per-table filters | UserDefaults | `FilterSettingsStorage` (saves `appliedFilters` only) |
171+
| Favorite tables | UserDefaults | `FavoriteTablesStorage` (per connection + database + schema; iCloud-synced) |
171172

172173
### Logging & Debugging
173174

TablePro.xcodeproj/project.pbxproj

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,13 @@
202202
remoteGlobalIDString = 5A1091C62EF17EDC0055EA7C;
203203
remoteInfo = TablePro;
204204
};
205+
5AF00A112FB9000000000001 /* PBXContainerItemProxy */ = {
206+
isa = PBXContainerItemProxy;
207+
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
208+
proxyType = 1;
209+
remoteGlobalIDString = 5A1091C62EF17EDC0055EA7C;
210+
remoteInfo = TablePro;
211+
};
205212
5ABQR00000000000000000C0 /* PBXContainerItemProxy */ = {
206213
isa = PBXContainerItemProxy;
207214
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
@@ -297,6 +304,7 @@
297304
5A87A000100000000 /* CassandraDriver.tableplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CassandraDriver.tableplugin; sourceTree = BUILT_PRODUCTS_DIR; };
298305
5ABBED792FB55E1400A78382 /* CSVInspectorPlugin.tableplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CSVInspectorPlugin.tableplugin; sourceTree = BUILT_PRODUCTS_DIR; };
299306
5ABCC5A72F43856700EAF3FC /* TableProTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TableProTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
307+
5AF00A102FB9000000000001 /* TableProUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TableProUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
300308
5ABQR00200000000000000A1 /* BigQueryAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigQueryAuth.swift; sourceTree = "<group>"; };
301309
5ABQR00200000000000000A2 /* BigQueryConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigQueryConnection.swift; sourceTree = "<group>"; };
302310
5ABQR00200000000000000A3 /* BigQueryPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigQueryPlugin.swift; sourceTree = "<group>"; };
@@ -677,6 +685,11 @@
677685
path = TableProTests;
678686
sourceTree = "<group>";
679687
};
688+
5AF00A122FB9000000000001 /* TableProUITests */ = {
689+
isa = PBXFileSystemSynchronizedRootGroup;
690+
path = TableProUITests;
691+
sourceTree = "<group>";
692+
};
680693
5AE4F4812F6BC0640097AC5B /* Plugins/CloudflareD1DriverPlugin */ = {
681694
isa = PBXFileSystemSynchronizedRootGroup;
682695
exceptions = (
@@ -708,6 +721,13 @@
708721
);
709722
runOnlyForDeploymentPostprocessing = 0;
710723
};
724+
5AF00A132FB9000000000001 /* Frameworks */ = {
725+
isa = PBXFrameworksBuildPhase;
726+
buildActionMask = 2147483647;
727+
files = (
728+
);
729+
runOnlyForDeploymentPostprocessing = 0;
730+
};
711731
5A3BE6F52F97DA8100611C1F /* Frameworks */ = {
712732
isa = PBXFrameworksBuildPhase;
713733
buildActionMask = 2147483647;
@@ -939,6 +959,7 @@
939959
5A86E000500000000 /* Plugins/MQLExportPlugin */,
940960
5A86F000500000000 /* Plugins/SQLImportPlugin */,
941961
5ABCC5A82F43856700EAF3FC /* TableProTests */,
962+
5AF00A122FB9000000000001 /* TableProUITests */,
942963
5A32BC012F9D5F1300BAEB5F /* mcp-server */,
943964
5A1091C82EF17EDC0055EA7C /* Products */,
944965
5A05FBC72F3EDF7500819CD7 /* Recovered References */,
@@ -968,6 +989,7 @@
968989
5A86E000100000000 /* MQLExport.tableplugin */,
969990
5A86F000100000000 /* SQLImport.tableplugin */,
970991
5ABCC5A72F43856700EAF3FC /* TableProTests.xctest */,
992+
5AF00A102FB9000000000001 /* TableProUITests.xctest */,
971993
5AEA8B2A2F6808270040461A /* EtcdDriverPlugin.tableplugin */,
972994
5ADDB00300000000000000A0 /* DynamoDBDriverPlugin.tableplugin */,
973995
5ABQR00300000000000000A0 /* BigQueryDriverPlugin.tableplugin */,
@@ -1524,6 +1546,27 @@
15241546
productReference = 5ABCC5A72F43856700EAF3FC /* TableProTests.xctest */;
15251547
productType = "com.apple.product-type.bundle.unit-test";
15261548
};
1549+
5AF00A142FB9000000000001 /* TableProUITests */ = {
1550+
isa = PBXNativeTarget;
1551+
buildConfigurationList = 5AF00A192FB9000000000001 /* Build configuration list for PBXNativeTarget "TableProUITests" */;
1552+
buildPhases = (
1553+
5AF00A152FB9000000000001 /* Sources */,
1554+
5AF00A132FB9000000000001 /* Frameworks */,
1555+
5AF00A162FB9000000000001 /* Resources */,
1556+
);
1557+
buildRules = (
1558+
);
1559+
dependencies = (
1560+
5AF00A172FB9000000000001 /* PBXTargetDependency */,
1561+
);
1562+
fileSystemSynchronizedGroups = (
1563+
5AF00A122FB9000000000001 /* TableProUITests */,
1564+
);
1565+
name = TableProUITests;
1566+
productName = TableProUITests;
1567+
productReference = 5AF00A102FB9000000000001 /* TableProUITests.xctest */;
1568+
productType = "com.apple.product-type.bundle.ui-testing";
1569+
};
15271570
5ABQR00600000000000000B0 /* BigQueryDriverPlugin */ = {
15281571
isa = PBXNativeTarget;
15291572
buildConfigurationList = 5ABQR00800000000000000B0 /* Build configuration list for PBXNativeTarget "BigQueryDriverPlugin" */;
@@ -1671,6 +1714,10 @@
16711714
CreatedOnToolsVersion = 26.2;
16721715
TestTargetID = 5A1091C62EF17EDC0055EA7C;
16731716
};
1717+
5AF00A142FB9000000000001 = {
1718+
CreatedOnToolsVersion = 26.5;
1719+
TestTargetID = 5A1091C62EF17EDC0055EA7C;
1720+
};
16741721
5AE4F4732F6BC0640097AC5B = {
16751722
CreatedOnToolsVersion = 26.3;
16761723
LastSwiftMigration = 2630;
@@ -1725,6 +1772,7 @@
17251772
5A86E000000000000 /* MQLExport */,
17261773
5A86F000000000000 /* SQLImport */,
17271774
5ABCC5A62F43856700EAF3FC /* TableProTests */,
1775+
5AF00A142FB9000000000001 /* TableProUITests */,
17281776
5AEA8B292F6808270040461A /* EtcdDriverPlugin */,
17291777
5AE4F4732F6BC0640097AC5B /* CloudflareD1DriverPlugin */,
17301778
5ADDB00600000000000000B0 /* DynamoDBDriverPlugin */,
@@ -1744,6 +1792,13 @@
17441792
);
17451793
runOnlyForDeploymentPostprocessing = 0;
17461794
};
1795+
5AF00A162FB9000000000001 /* Resources */ = {
1796+
isa = PBXResourcesBuildPhase;
1797+
buildActionMask = 2147483647;
1798+
files = (
1799+
);
1800+
runOnlyForDeploymentPostprocessing = 0;
1801+
};
17471802
5A3BE6F62F97DA8100611C1F /* Resources */ = {
17481803
isa = PBXResourcesBuildPhase;
17491804
buildActionMask = 2147483647;
@@ -1929,6 +1984,13 @@
19291984
);
19301985
runOnlyForDeploymentPostprocessing = 0;
19311986
};
1987+
5AF00A152FB9000000000001 /* Sources */ = {
1988+
isa = PBXSourcesBuildPhase;
1989+
buildActionMask = 2147483647;
1990+
files = (
1991+
);
1992+
runOnlyForDeploymentPostprocessing = 0;
1993+
};
19321994
5A3BE6F42F97DA8100611C1F /* Sources */ = {
19331995
isa = PBXSourcesBuildPhase;
19341996
buildActionMask = 2147483647;
@@ -2212,6 +2274,11 @@
22122274
target = 5A1091C62EF17EDC0055EA7C /* TablePro */;
22132275
targetProxy = 5ABCC5AB2F43856700EAF3FC /* PBXContainerItemProxy */;
22142276
};
2277+
5AF00A172FB9000000000001 /* PBXTargetDependency */ = {
2278+
isa = PBXTargetDependency;
2279+
target = 5A1091C62EF17EDC0055EA7C /* TablePro */;
2280+
targetProxy = 5AF00A112FB9000000000001 /* PBXContainerItemProxy */;
2281+
};
22152282
5ABQR00000000000000000C1 /* PBXTargetDependency */ = {
22162283
isa = PBXTargetDependency;
22172284
target = 5ABQR00600000000000000B0 /* BigQueryDriverPlugin */;
@@ -3713,6 +3780,48 @@
37133780
};
37143781
name = Release;
37153782
};
3783+
5AF00A182FB9000000000001 /* Debug */ = {
3784+
isa = XCBuildConfiguration;
3785+
buildSettings = {
3786+
CODE_SIGN_STYLE = Automatic;
3787+
CURRENT_PROJECT_VERSION = 1;
3788+
DEVELOPMENT_TEAM = "";
3789+
GENERATE_INFOPLIST_FILE = YES;
3790+
MACOSX_DEPLOYMENT_TARGET = 14.0;
3791+
MARKETING_VERSION = 1.0;
3792+
PRODUCT_BUNDLE_IDENTIFIER = com.TablePro.TableProUITests;
3793+
PRODUCT_NAME = "$(TARGET_NAME)";
3794+
SDKROOT = macosx;
3795+
STRING_CATALOG_GENERATE_SYMBOLS = NO;
3796+
SWIFT_APPROACHABLE_CONCURRENCY = YES;
3797+
SWIFT_EMIT_LOC_STRINGS = NO;
3798+
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
3799+
SWIFT_VERSION = 5.9;
3800+
TEST_TARGET_NAME = TablePro;
3801+
};
3802+
name = Debug;
3803+
};
3804+
5AF00A1A2FB9000000000001 /* Release */ = {
3805+
isa = XCBuildConfiguration;
3806+
buildSettings = {
3807+
CODE_SIGN_STYLE = Automatic;
3808+
CURRENT_PROJECT_VERSION = 1;
3809+
DEVELOPMENT_TEAM = "";
3810+
GENERATE_INFOPLIST_FILE = YES;
3811+
MACOSX_DEPLOYMENT_TARGET = 14.0;
3812+
MARKETING_VERSION = 1.0;
3813+
PRODUCT_BUNDLE_IDENTIFIER = com.TablePro.TableProUITests;
3814+
PRODUCT_NAME = "$(TARGET_NAME)";
3815+
SDKROOT = macosx;
3816+
STRING_CATALOG_GENERATE_SYMBOLS = NO;
3817+
SWIFT_APPROACHABLE_CONCURRENCY = YES;
3818+
SWIFT_EMIT_LOC_STRINGS = NO;
3819+
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
3820+
SWIFT_VERSION = 5.9;
3821+
TEST_TARGET_NAME = TablePro;
3822+
};
3823+
name = Release;
3824+
};
37163825
5ABQR00700000000000000B1 /* Debug */ = {
37173826
isa = XCBuildConfiguration;
37183827
buildSettings = {
@@ -4116,6 +4225,15 @@
41164225
defaultConfigurationIsVisible = 0;
41174226
defaultConfigurationName = Release;
41184227
};
4228+
5AF00A192FB9000000000001 /* Build configuration list for PBXNativeTarget "TableProUITests" */ = {
4229+
isa = XCConfigurationList;
4230+
buildConfigurations = (
4231+
5AF00A182FB9000000000001 /* Debug */,
4232+
5AF00A1A2FB9000000000001 /* Release */,
4233+
);
4234+
defaultConfigurationIsVisible = 0;
4235+
defaultConfigurationName = Release;
4236+
};
41194237
5ABQR00800000000000000B0 /* Build configuration list for PBXNativeTarget "BigQueryDriverPlugin" */ = {
41204238
isa = XCConfigurationList;
41214239
buildConfigurations = (

TablePro.xcodeproj/xcshareddata/xcschemes/TablePro.xcscheme

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@
4141
ReferencedContainer = "container:TablePro.xcodeproj">
4242
</BuildableReference>
4343
</TestableReference>
44+
<TestableReference
45+
skipped = "NO"
46+
parallelizable = "YES">
47+
<BuildableReference
48+
BuildableIdentifier = "primary"
49+
BlueprintIdentifier = "5AF00A142FB9000000000001"
50+
BuildableName = "TableProUITests.xctest"
51+
BlueprintName = "TableProUITests"
52+
ReferencedContainer = "container:TablePro.xcodeproj">
53+
</BuildableReference>
54+
</TestableReference>
4455
</Testables>
4556
</TestAction>
4657
<LaunchAction

TablePro/AppDelegate.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
1616

1717
private var hasRunPostLaunchActivation = false
1818

19+
private static var isUITesting: Bool {
20+
ProcessInfo.processInfo.environment["TABLEPRO_UI_TESTING"] == "1"
21+
}
22+
1923
// MARK: - URL & File Open
2024

2125
func applicationWillFinishLaunching(_ notification: Notification) {
@@ -93,12 +97,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
9397

9498
func applicationDidBecomeActive(_ notification: Notification) {
9599
runPostLaunchActivationIfNeeded()
100+
guard !Self.isUITesting else { return }
96101
SyncCoordinator.shared.syncIfNeeded()
97102
}
98103

99104
private func runPostLaunchActivationIfNeeded() {
100105
guard !hasRunPostLaunchActivation else { return }
101106
hasRunPostLaunchActivation = true
107+
guard !Self.isUITesting else { return }
102108

103109
ConnectionStorage.shared.migratePluginSecureFieldsIfNeeded()
104110
AnalyticsService.shared.startPeriodicHeartbeat()

TablePro/Core/Services/AppServices.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct AppServices {
1616
let schemaService: SchemaService
1717
let schemaProviderRegistry: SchemaProviderRegistry
1818
let sqlFavoriteManager: SQLFavoriteManager
19+
let favoriteTablesStorage: FavoriteTablesStorage
1920
let aiChatStorage: AIChatStorage
2021
let aiKeyStorage: AIKeyStorage
2122
let groupStorage: GroupStorage
@@ -43,6 +44,7 @@ struct AppServices {
4344
schemaService: .shared,
4445
schemaProviderRegistry: .shared,
4546
sqlFavoriteManager: .shared,
47+
favoriteTablesStorage: .shared,
4648
aiChatStorage: .shared,
4749
aiKeyStorage: .shared,
4850
groupStorage: .shared,

0 commit comments

Comments
 (0)