Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a764ef6
Update .gitignore
J2TeamNNL May 25, 2026
bc530fd
Update CLAUDE.md
J2TeamNNL May 25, 2026
97c7f11
Update .gitignore
J2TeamNNL May 27, 2026
b1933e9
Merge branch 'TableProApp:main' into main
J2TeamNNL May 28, 2026
2314859
feat(sidebar): add favorite tables
J2TeamNNL May 25, 2026
7c796d2
feat(sidebar): add recent tables, star toggle, create-table button, o…
J2TeamNNL May 25, 2026
81c5000
feat(sidebar): handle tableFavorite in conflict resolution, fix showE…
J2TeamNNL May 26, 2026
7692a1d
Update .gitignore
J2TeamNNL May 26, 2026
124203d
refactor(sidebar): remove ER diagram context menu item, drop SidebarT…
J2TeamNNL May 26, 2026
ea4aae6
fix(sidebar): address PR review blockers and design concerns
J2TeamNNL May 28, 2026
470739c
fix(sidebar): address review — node-id schema, list selection, lock i…
J2TeamNNL May 29, 2026
8fcc894
fix(sidebar): scope table favorites by database
datlechin May 29, 2026
8bd677b
fix(sidebar): show create-table button only on the Tables tab
datlechin May 29, 2026
8213f68
fix(launch): skip live iCloud sync under TABLEPRO_UI_TESTING
datlechin May 29, 2026
86c4a53
refactor(sidebar): drop unused RecentTablesStore lastAccessedAt
datlechin May 29, 2026
fd7378d
Merge remote-tracking branch 'origin/main' into sidebar
datlechin May 29, 2026
ebb78dd
fix(sidebar): use system colors in TableRowLogic to match color tests
datlechin May 29, 2026
f789ac0
Merge remote-tracking branch 'origin/main' into sidebar
datlechin May 29, 2026
9412d92
Merge branch 'main' into sidebar
datlechin May 29, 2026
6bc3da1
refactor(sidebar): move create-table action into the sidebar bottom bar
datlechin May 29, 2026
be936af
refactor(sidebar): reveal favorite star on hover and refine favorites…
datlechin May 29, 2026
e2aa2a7
fix(sidebar): drop hardcoded footer divider, use native bottom bar (s…
datlechin May 29, 2026
21b4d7d
fix(sidebar): footer inherits sidebar vibrancy instead of opaque mate…
datlechin May 29, 2026
22ea00f
fix(sidebar): use hard scroll-edge style so footer divider appears on…
datlechin May 29, 2026
a454140
refactor(sidebar): use a static footer divider, drop scroll-edge expe…
datlechin May 29, 2026
f35b32d
refactor(sidebar): remove the Recent tables section
datlechin May 29, 2026
2f400fe
fix(sidebar): drop duplicate context menu and add accessibility label…
datlechin May 29, 2026
c07ce00
refactor(sidebar): type-safe favorite selection and drop AnyView from…
datlechin May 29, 2026
b2a96b4
fix(test): use explicit self for connectionStorage in GroupStorageTes…
datlechin May 29, 2026
9f5fea2
test(sidebar): cover FavoriteSelection round-trip and scope table sel…
datlechin May 29, 2026
d6bba87
feat(sidebar): restore recent tables section
J2TeamNNL May 29, 2026
d03a6da
feat(settings): add toggle for the sidebar recent tables section
J2TeamNNL May 29, 2026
19b4549
refactor(sidebar): derive recent entry id from Entry.id, cover clearAll
J2TeamNNL May 29, 2026
061ac89
Merge remote-tracking branch 'upstream/main' into recent-tables
J2TeamNNL Jun 2, 2026
2835b29
Merge remote-tracking branch 'upstream/main' into recent-tables
J2TeamNNL Jun 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,6 @@ fix-1322-plugin-abi-and-registry-overhaul.diff
# Issue analysis blueprints (local only)
.analysis/
.docs/
Local.xcconfig

# Plans (local only)
/plans/reports
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Importing connections from other apps now detects duplicates by host, port, database, and username, and lets you replace, add a copy, or skip each one before import.
- Oracle connections negotiate Native Network Encryption when the server asks for it, so servers with `SQLNET.ENCRYPTION_SERVER` or `SQLNET.CRYPTO_CHECKSUM_SERVER` set to REQUIRED now connect (AES with a SHA crypto-checksum), matching what SQL Developer and DBeaver do. (#483)
- Oracle connections follow listener redirects, so RAC SCAN listeners, shared server, and load-balanced setups now connect instead of failing during the handshake. (#483)
- Recent section at the top of the Tables sidebar tracks the last 10 tables you opened per connection and database, in-memory for the session. Off by default, turn it on in Settings > General > Sidebar. (#1352)

### Changed

Expand All @@ -23,6 +24,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Cmd+N now opens a new connection; Manage Connections keeps its File menu item.
- First Page and Last Page now default to Cmd+Option+Up and Cmd+Option+Down.
- Shortcuts can be bound to function keys (F1 through F12), with or without a modifier.
- 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.
- The window minimum width now adjusts to the visible panes, so opening the inspector on a small window no longer pushes content off-screen.

### Removed

- "Create New Table…" from the sidebar right-click menu. Use the plus button in the Tables sidebar footer instead.

### Fixed

Expand Down
115 changes: 115 additions & 0 deletions TablePro.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@
remoteGlobalIDString = 5A1091C62EF17EDC0055EA7C;
remoteInfo = TablePro;
};
5AF00A112FB9000000000001 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
proxyType = 1;
remoteGlobalIDString = 5A1091C62EF17EDC0055EA7C;
remoteInfo = TablePro;
};
5ABQR00000000000000000C0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
Expand Down Expand Up @@ -315,6 +322,7 @@
5A87A000100000000 /* CassandraDriver.tableplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CassandraDriver.tableplugin; sourceTree = BUILT_PRODUCTS_DIR; };
5ABBED792FB55E1400A78382 /* CSVInspectorPlugin.tableplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CSVInspectorPlugin.tableplugin; sourceTree = BUILT_PRODUCTS_DIR; };
5ABCC5A72F43856700EAF3FC /* TableProTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TableProTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5AF00A102FB9000000000001 /* TableProUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TableProUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5ABQR00200000000000000A1 /* BigQueryAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigQueryAuth.swift; sourceTree = "<group>"; };
5ABQR00200000000000000A2 /* BigQueryConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigQueryConnection.swift; sourceTree = "<group>"; };
5ABQR00200000000000000A3 /* BigQueryPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigQueryPlugin.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -719,6 +727,11 @@
path = TableProTests;
sourceTree = "<group>";
};
5AF00A122FB9000000000001 /* TableProUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = TableProUITests;
sourceTree = "<group>";
};
5AE4F4812F6BC0640097AC5B /* Plugins/CloudflareD1DriverPlugin */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
Expand Down Expand Up @@ -755,6 +768,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
5AF00A132FB9000000000001 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
5A3BE6F52F97DA8100611C1F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -1611,6 +1631,27 @@
productReference = 5ABCC5A72F43856700EAF3FC /* TableProTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
5AF00A142FB9000000000001 /* TableProUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5AF00A192FB9000000000001 /* Build configuration list for PBXNativeTarget "TableProUITests" */;
buildPhases = (
5AF00A152FB9000000000001 /* Sources */,
5AF00A132FB9000000000001 /* Frameworks */,
5AF00A162FB9000000000001 /* Resources */,
);
buildRules = (
);
dependencies = (
5AF00A172FB9000000000001 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
5AF00A122FB9000000000001 /* TableProUITests */,
);
name = TableProUITests;
productName = TableProUITests;
productReference = 5AF00A102FB9000000000001 /* TableProUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
5ABQR00600000000000000B0 /* BigQueryDriverPlugin */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5ABQR00800000000000000B0 /* Build configuration list for PBXNativeTarget "BigQueryDriverPlugin" */;
Expand Down Expand Up @@ -1779,6 +1820,10 @@
CreatedOnToolsVersion = 26.2;
TestTargetID = 5A1091C62EF17EDC0055EA7C;
};
5AF00A142FB9000000000001 = {
CreatedOnToolsVersion = 26.5;
TestTargetID = 5A1091C62EF17EDC0055EA7C;
};
5AE4F4732F6BC0640097AC5B = {
CreatedOnToolsVersion = 26.3;
LastSwiftMigration = 2630;
Expand Down Expand Up @@ -1858,6 +1903,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
5AF00A162FB9000000000001 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
5A3BE6F62F97DA8100611C1F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -2057,6 +2109,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
5AF00A152FB9000000000001 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
5A3BE6F42F97DA8100611C1F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -2359,6 +2418,11 @@
target = 5A1091C62EF17EDC0055EA7C /* TablePro */;
targetProxy = 5ABCC5AB2F43856700EAF3FC /* PBXContainerItemProxy */;
};
5AF00A172FB9000000000001 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 5A1091C62EF17EDC0055EA7C /* TablePro */;
targetProxy = 5AF00A112FB9000000000001 /* PBXContainerItemProxy */;
};
5ABQR00000000000000000C1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 5ABQR00600000000000000B0 /* BigQueryDriverPlugin */;
Expand Down Expand Up @@ -3913,6 +3977,48 @@
};
name = Release;
};
5AF00A182FB9000000000001 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.TablePro.TableProUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.9;
TEST_TARGET_NAME = TablePro;
};
name = Debug;
};
5AF00A1A2FB9000000000001 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.TablePro.TableProUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.9;
TEST_TARGET_NAME = TablePro;
};
name = Release;
};
5ABQR00700000000000000B1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
Expand Down Expand Up @@ -4367,6 +4473,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
5AF00A192FB9000000000001 /* Build configuration list for PBXNativeTarget "TableProUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5AF00A182FB9000000000001 /* Debug */,
5AF00A1A2FB9000000000001 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
5ABQR00800000000000000B0 /* Build configuration list for PBXNativeTarget "BigQueryDriverPlugin" */ = {
isa = XCConfigurationList;
buildConfigurations = (
Expand Down
57 changes: 57 additions & 0 deletions TablePro/Core/Storage/RecentTablesStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Foundation

extension Notification.Name {
static let recentTablesDidChange = Notification.Name("RecentTablesDidChange")
}

@MainActor
final class RecentTablesStore {
static let shared = RecentTablesStore()

struct Key: Hashable {
let connectionID: UUID
let database: String?
}

struct Entry: Hashable, Identifiable {
let name: String
let schema: String?
let type: TableInfo.TableType

var id: String { schema.map { "\($0).\(name)" } ?? name }
}

private var entriesByKey: [Key: [Entry]] = [:]
private let cap = 10

init() {}

func push(connectionID: UUID, database: String?, table: TableInfo) {
let key = Key(connectionID: connectionID, database: database)
let entry = Entry(name: table.name, schema: table.schema, type: table.type)
var list = entriesByKey[key] ?? []
list.removeAll { $0.id == entry.id }
list.insert(entry, at: 0)
if list.count > cap {
list = Array(list.prefix(cap))
}
entriesByKey[key] = list
NotificationCenter.default.post(name: .recentTablesDidChange, object: nil)
}

func entries(connectionID: UUID, database: String?) -> [Entry] {
entriesByKey[Key(connectionID: connectionID, database: database)] ?? []
}

func clear(connectionID: UUID, database: String?) {
entriesByKey.removeValue(forKey: Key(connectionID: connectionID, database: database))
NotificationCenter.default.post(name: .recentTablesDidChange, object: nil)
}

func clearAll() {
entriesByKey.removeAll()
NotificationCenter.default.post(name: .recentTablesDidChange, object: nil)
}

var cappedSize: Int { cap }
}
11 changes: 9 additions & 2 deletions TablePro/Models/Settings/GeneralSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,32 @@ struct GeneralSettings: Codable, Equatable {
/// Whether to share anonymous usage analytics
var shareAnalytics: Bool

/// Whether the sidebar shows a Recent section with recently opened tables
var showRecentTables: Bool

static let `default` = GeneralSettings(
startupBehavior: .showWelcome,
language: .system,
automaticallyCheckForUpdates: true,
queryTimeoutSeconds: 60,
shareAnalytics: true
shareAnalytics: true,
showRecentTables: false
)

init(
startupBehavior: StartupBehavior = .showWelcome,
language: AppLanguage = .system,
automaticallyCheckForUpdates: Bool = true,
queryTimeoutSeconds: Int = 60,
shareAnalytics: Bool = true
shareAnalytics: Bool = true,
showRecentTables: Bool = false
) {
self.startupBehavior = startupBehavior
self.language = language
self.automaticallyCheckForUpdates = automaticallyCheckForUpdates
self.queryTimeoutSeconds = queryTimeoutSeconds
self.shareAnalytics = shareAnalytics
self.showRecentTables = showRecentTables
}

init(from decoder: Decoder) throws {
Expand All @@ -90,5 +96,6 @@ struct GeneralSettings: Codable, Equatable {
automaticallyCheckForUpdates = try container.decodeIfPresent(Bool.self, forKey: .automaticallyCheckForUpdates) ?? true
queryTimeoutSeconds = try container.decodeIfPresent(Int.self, forKey: .queryTimeoutSeconds) ?? 60
shareAnalytics = try container.decodeIfPresent(Bool.self, forKey: .shareAnalytics) ?? true
showRecentTables = try container.decodeIfPresent(Bool.self, forKey: .showRecentTables) ?? false
}
}
11 changes: 4 additions & 7 deletions TablePro/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1942,7 +1942,6 @@
}
},
"%lld of %lld" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
Expand Down Expand Up @@ -13417,6 +13416,10 @@
}
}
},
"Create New Table" : {
"comment" : "Tooltip and accessibility label for the button that allows the user to create a new table.",
"isCommentAutoGenerated" : true
},
"Create New Table..." : {
"extractionState" : "stale",
"localizations" : {
Expand Down Expand Up @@ -22579,7 +22582,6 @@
}
},
"Format JSON" : {
"extractionState" : "stale",
"localizations" : {
"tr" : {
"stringUnit" : {
Expand Down Expand Up @@ -27466,7 +27468,6 @@
}
},
"Limit" : {
"extractionState" : "stale",
"localizations" : {
"tr" : {
"stringUnit" : {
Expand Down Expand Up @@ -30427,7 +30428,6 @@
}
},
"Next Page (⌘])" : {
"extractionState" : "stale",
"localizations" : {
"tr" : {
"stringUnit" : {
Expand Down Expand Up @@ -32876,7 +32876,6 @@
}
},
"Offset" : {
"extractionState" : "stale",
"localizations" : {
"tr" : {
"stringUnit" : {
Expand Down Expand Up @@ -34134,7 +34133,6 @@
}
},
"Pagination Settings" : {
"extractionState" : "stale",
"localizations" : {
"tr" : {
"stringUnit" : {
Expand Down Expand Up @@ -36189,7 +36187,6 @@
}
},
"Previous Page (⌘[)" : {
"extractionState" : "stale",
"localizations" : {
"tr" : {
"stringUnit" : {
Expand Down
1 change: 1 addition & 0 deletions TablePro/ViewModels/SidebarViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ final class SidebarViewModel {
)
}
}
var isRecentsExpanded: Bool = true
var redisKeyTreeViewModel: RedisKeyTreeViewModel?
var showOperationDialog = false
var pendingOperationType: TableOperationType?
Expand Down
Loading
Loading