diff --git a/Plugins/BigQueryDriverPlugin/BigQueryPlugin.swift b/Plugins/BigQueryDriverPlugin/BigQueryPlugin.swift index ded1d91f9..65d6a87c3 100644 --- a/Plugins/BigQueryDriverPlugin/BigQueryPlugin.swift +++ b/Plugins/BigQueryDriverPlugin/BigQueryPlugin.swift @@ -41,6 +41,7 @@ final class BigQueryPlugin: NSObject, TableProPlugin, DriverPlugin { static let supportsSSH = false static let supportsSSL = false static let tableEntityName = "Tables" + static let containerEntityName = "Dataset" static let supportsForeignKeyDisable = false static let supportsReadOnlyMode = true static let databaseGroupingStrategy: GroupingStrategy = .bySchema diff --git a/Plugins/TableProPluginKit/DriverPlugin.swift b/Plugins/TableProPluginKit/DriverPlugin.swift index 466fd3246..e84261650 100644 --- a/Plugins/TableProPluginKit/DriverPlugin.swift +++ b/Plugins/TableProPluginKit/DriverPlugin.swift @@ -37,6 +37,7 @@ public protocol DriverPlugin: TableProPlugin { static var sqlDialect: SQLDialectDescriptor? { get } static var statementCompletions: [CompletionEntry] { get } static var tableEntityName: String { get } + static var containerEntityName: String { get } static var supportsCascadeDrop: Bool { get } static var supportsForeignKeyDisable: Bool { get } static var immutableColumns: [String] { get } @@ -95,6 +96,7 @@ public extension DriverPlugin { static var sqlDialect: SQLDialectDescriptor? { nil } static var statementCompletions: [CompletionEntry] { [] } static var tableEntityName: String { "Tables" } + static var containerEntityName: String { "Database" } static var supportsCascadeDrop: Bool { false } static var supportsForeignKeyDisable: Bool { true } static var immutableColumns: [String] { [] } diff --git a/TablePro/ContentView.swift b/TablePro/ContentView.swift index 11d5425ba..750c6f80b 100644 --- a/TablePro/ContentView.swift +++ b/TablePro/ContentView.swift @@ -116,6 +116,7 @@ struct ContentView: View { AppState.shared.currentDatabaseType = session.connection.type AppState.shared.supportsDatabaseSwitching = PluginManager.shared.supportsDatabaseSwitching( for: session.connection.type) + || PluginManager.shared.supportsSchemaSwitching(for: session.connection.type) } } else { currentSession = nil @@ -156,6 +157,7 @@ struct ContentView: View { AppState.shared.currentDatabaseType = session.connection.type AppState.shared.supportsDatabaseSwitching = PluginManager.shared.supportsDatabaseSwitching( for: session.connection.type) + || PluginManager.shared.supportsSchemaSwitching(for: session.connection.type) } else { AppState.shared.isConnected = false AppState.shared.safeModeLevel = .silent @@ -408,6 +410,7 @@ struct ContentView: View { AppState.shared.currentDatabaseType = newSession.connection.type AppState.shared.supportsDatabaseSwitching = PluginManager.shared.supportsDatabaseSwitching( for: newSession.connection.type) + || PluginManager.shared.supportsSchemaSwitching(for: newSession.connection.type) } // MARK: - Actions diff --git a/TablePro/Core/Plugins/PluginManager.swift b/TablePro/Core/Plugins/PluginManager.swift index 569f2d66f..239233cc6 100644 --- a/TablePro/Core/Plugins/PluginManager.swift +++ b/TablePro/Core/Plugins/PluginManager.swift @@ -811,6 +811,11 @@ final class PluginManager { .schema.tableEntityName ?? "Tables" } + func containerEntityName(for databaseType: DatabaseType) -> String { + PluginMetadataRegistry.shared.snapshot(forTypeId: databaseType.pluginTypeId)? + .schema.containerEntityName ?? "Database" + } + func supportsCascadeDrop(for databaseType: DatabaseType) -> Bool { PluginMetadataRegistry.shared.snapshot(forTypeId: databaseType.pluginTypeId)? .capabilities.supportsCascadeDrop ?? false diff --git a/TablePro/Core/Plugins/PluginMetadataRegistry+CloudDefaults.swift b/TablePro/Core/Plugins/PluginMetadataRegistry+CloudDefaults.swift index 5a569b3bd..9d57a5de4 100644 --- a/TablePro/Core/Plugins/PluginMetadataRegistry+CloudDefaults.swift +++ b/TablePro/Core/Plugins/PluginMetadataRegistry+CloudDefaults.swift @@ -37,6 +37,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: [], @@ -177,6 +178,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "", defaultGroupName: "default", tableEntityName: "Tables", + containerEntityName: "Dataset", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: [], diff --git a/TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift b/TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift index 91ba91c42..7912dba62 100644 --- a/TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift +++ b/TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift @@ -533,6 +533,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Collections", + containerEntityName: "Database", defaultPrimaryKeyColumn: "_id", immutableColumns: ["_id"], systemDatabaseNames: ["admin", "local", "config"], @@ -604,6 +605,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "public", defaultGroupName: "db0", tableEntityName: "Keys", + containerEntityName: "Database", defaultPrimaryKeyColumn: "Key", immutableColumns: [], systemDatabaseNames: [], @@ -644,6 +646,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "dbo", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: ["master", "tempdb", "model", "msdb"], @@ -691,6 +694,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: [ @@ -747,6 +751,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: ["information_schema", "INFORMATION_SCHEMA", "system"], @@ -777,6 +782,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: ["information_schema", "pg_catalog"], @@ -819,6 +825,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "public", defaultGroupName: "default", tableEntityName: "Tables", + containerEntityName: "Keyspace", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: [ @@ -873,6 +880,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "public", defaultGroupName: "default", tableEntityName: "Tables", + containerEntityName: "Keyspace", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: [ @@ -926,6 +934,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Keys", + containerEntityName: "Database", defaultPrimaryKeyColumn: "Key", immutableColumns: ["Version", "ModRevision", "CreateRevision"], systemDatabaseNames: [], @@ -1008,6 +1017,7 @@ extension PluginMetadataRegistry { defaultSchemaName: "main", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: [], diff --git a/TablePro/Core/Plugins/PluginMetadataRegistry.swift b/TablePro/Core/Plugins/PluginMetadataRegistry.swift index f8bc43ec0..63a68c673 100644 --- a/TablePro/Core/Plugins/PluginMetadataRegistry.swift +++ b/TablePro/Core/Plugins/PluginMetadataRegistry.swift @@ -69,6 +69,7 @@ struct PluginMetadataSnapshot: Sendable { let defaultSchemaName: String let defaultGroupName: String let tableEntityName: String + let containerEntityName: String let defaultPrimaryKeyColumn: String? let immutableColumns: [String] let systemDatabaseNames: [String] @@ -81,6 +82,7 @@ struct PluginMetadataSnapshot: Sendable { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: [], @@ -348,6 +350,7 @@ final class PluginMetadataRegistry: @unchecked Sendable { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: ["information_schema", "mysql", "performance_schema", "sys"], @@ -378,6 +381,7 @@ final class PluginMetadataRegistry: @unchecked Sendable { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: ["information_schema", "mysql", "performance_schema", "sys"], @@ -419,6 +423,7 @@ final class PluginMetadataRegistry: @unchecked Sendable { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: ["postgres", "template0", "template1"], @@ -462,6 +467,7 @@ final class PluginMetadataRegistry: @unchecked Sendable { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: ["postgres", "template0", "template1"], @@ -505,6 +511,7 @@ final class PluginMetadataRegistry: @unchecked Sendable { defaultSchemaName: "public", defaultGroupName: "main", tableEntityName: "Tables", + containerEntityName: "Database", defaultPrimaryKeyColumn: nil, immutableColumns: [], systemDatabaseNames: [], @@ -664,6 +671,7 @@ final class PluginMetadataRegistry: @unchecked Sendable { defaultSchemaName: driverType.defaultSchemaName, defaultGroupName: driverType.defaultGroupName, tableEntityName: driverType.tableEntityName, + containerEntityName: driverType.containerEntityName, defaultPrimaryKeyColumn: driverType.defaultPrimaryKeyColumn, immutableColumns: driverType.immutableColumns, systemDatabaseNames: driverType.systemDatabaseNames, diff --git a/TablePro/Resources/Localizable.xcstrings b/TablePro/Resources/Localizable.xcstrings index e164e87ef..cd73fcf90 100644 --- a/TablePro/Resources/Localizable.xcstrings +++ b/TablePro/Resources/Localizable.xcstrings @@ -251,6 +251,7 @@ } }, "(%lld active)" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -3016,8 +3017,12 @@ } } } + }, + "Add Filter" : { + }, "Add Filter (Cmd+Shift+F)" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -3038,6 +3043,9 @@ } } } + }, + "Add filter row" : { + }, "Add Folder..." : { "localizations" : { @@ -4050,8 +4058,12 @@ } } } + }, + "Apply" : { + }, "Apply All" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -4094,8 +4106,12 @@ } } } + }, + "Apply filters" : { + }, "Apply this filter" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -4118,6 +4134,7 @@ } }, "Apply This Filter" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -5978,6 +5995,7 @@ } }, "Clear search" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -6000,6 +6018,7 @@ } }, "Clear Search" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -6333,6 +6352,9 @@ } } } + }, + "Close preview" : { + }, "Close Tab" : { "localizations" : { @@ -6491,6 +6513,7 @@ } }, "Column" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -7660,6 +7683,9 @@ } } } + }, + "Copied to clipboard" : { + }, "Copied!" : { "localizations" : { @@ -10791,6 +10817,7 @@ } }, "Duplicate filter" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -13835,8 +13862,12 @@ } } } + }, + "Filter column" : { + }, "Filter column: %@" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -13901,8 +13932,12 @@ } } } + }, + "Filter operator" : { + }, "Filter operator: %@" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -13923,8 +13958,12 @@ } } } + }, + "Filter options" : { + }, "Filter presets" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -13947,6 +13986,7 @@ } }, "Filter settings" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -13969,6 +14009,7 @@ } }, "Filter Settings" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -13989,6 +14030,12 @@ } } } + }, + "Filter Settings..." : { + + }, + "Filter value" : { + }, "Filter with column" : { "localizations" : { @@ -14897,6 +14944,7 @@ } }, "History Limit:" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -21710,6 +21758,12 @@ } } } + }, + "Open File" : { + + }, + "Open File..." : { + }, "Open MQL Editor" : { "extractionState" : "stale", @@ -23482,6 +23536,9 @@ } } } + }, + "Preview Query" : { + }, "Preview Schema Changes" : { "localizations" : { @@ -24221,6 +24278,7 @@ } }, "Query History:" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -24287,6 +24345,7 @@ } }, "Quick search across all columns..." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -25072,6 +25131,9 @@ } } } + }, + "Remove all filters and reload" : { + }, "Remove filter" : { "localizations" : { @@ -25116,6 +25178,9 @@ } } } + }, + "Remove filter row" : { + }, "Remove Folder" : { "localizations" : { @@ -26182,6 +26247,7 @@ } }, "Save and load filter presets" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -26224,6 +26290,9 @@ } } } + }, + "Save As" : { + }, "Save as Favorite" : { "localizations" : { @@ -26313,6 +26382,9 @@ } } } + }, + "Save As..." : { + }, "Save Changes" : { "localizations" : { @@ -26473,6 +26545,9 @@ } } } + }, + "Save SQL file" : { + }, "Save Table Template" : { "extractionState" : "stale", @@ -26895,6 +26970,9 @@ } } } + }, + "Second filter value" : { + }, "Second value is required for BETWEEN" : { "localizations" : { @@ -27213,8 +27291,12 @@ } } } + }, + "Select filter column" : { + }, "Select filter for %@" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -27235,6 +27317,9 @@ } } } + }, + "Select filter operator" : { + }, "Select Plugin" : { "localizations" : { @@ -27280,6 +27365,9 @@ } } } + }, + "Select SQL files to open" : { + }, "Select Tab %lld" : { "localizations" : { @@ -30058,6 +30146,7 @@ } }, "Syncs connections, settings, and history across your Macs via iCloud." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -30078,6 +30167,9 @@ } } } + }, + "Syncs connections, settings, and SSH profiles across your Macs via iCloud." : { + }, "Syncs passwords via iCloud Keychain (end-to-end encrypted)." : { "localizations" : { @@ -34125,6 +34217,9 @@ } } } + }, + "WHERE clause" : { + }, "WHERE clause..." : { "localizations" : { diff --git a/TablePro/TableProApp.swift b/TablePro/TableProApp.swift index 3a7ba328d..641916292 100644 --- a/TablePro/TableProApp.swift +++ b/TablePro/TableProApp.swift @@ -176,7 +176,7 @@ struct AppMenuCommands: Commands { } .disabled(!appState.isConnected || appState.isReadOnly) - Button("Open Database...") { + Button("Open \(AppState.shared.currentDatabaseType.map { PluginManager.shared.containerEntityName(for: $0) } ?? "Database")...") { actions?.openDatabaseSwitcher() } .optionalKeyboardShortcut(shortcut(for: .openDatabase)) diff --git a/TablePro/Views/DatabaseSwitcher/DatabaseSwitcherSheet.swift b/TablePro/Views/DatabaseSwitcher/DatabaseSwitcherSheet.swift index 24c0028b5..0eeff7e10 100644 --- a/TablePro/Views/DatabaseSwitcher/DatabaseSwitcherSheet.swift +++ b/TablePro/Views/DatabaseSwitcher/DatabaseSwitcherSheet.swift @@ -63,14 +63,14 @@ struct DatabaseSwitcherSheet: View { var body: some View { VStack(spacing: 0) { // Header - Text(isSchemaMode - ? String(localized: "Open Schema") - : String(localized: "Open Database")) + Text("Open \(PluginManager.shared.containerEntityName(for: databaseType))") .font(.system(size: ThemeEngine.shared.activeTheme.typography.body, weight: .semibold)) .padding(.vertical, 12) - // Databases / Schemas toggle (PostgreSQL only) - if PluginManager.shared.supportsSchemaSwitching(for: databaseType) { + // Databases / Schemas toggle — only show when both modes are available + if PluginManager.shared.supportsSchemaSwitching(for: databaseType) + && PluginManager.shared.supportsDatabaseSwitching(for: databaseType) + { Picker("", selection: $viewModel.mode) { Text(String(localized: "Databases")) .tag(DatabaseSwitcherViewModel.Mode.database) diff --git a/TablePro/Views/Main/Child/MainEditorContentView.swift b/TablePro/Views/Main/Child/MainEditorContentView.swift index bf815934c..a7d96206c 100644 --- a/TablePro/Views/Main/Child/MainEditorContentView.swift +++ b/TablePro/Views/Main/Child/MainEditorContentView.swift @@ -600,7 +600,7 @@ struct MainEditorContentView: View { RoundedRectangle(cornerRadius: 4) .fill(Color(nsColor: .quaternaryLabelColor)) ) - Text("Switch Database") + Text("Switch \(PluginManager.shared.containerEntityName(for: connection.type))") .font(.callout) .foregroundStyle(.tertiary) } diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+Navigation.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+Navigation.swift index ae8b7844e..6f3477b25 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+Navigation.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+Navigation.swift @@ -411,7 +411,7 @@ extension MainContentCoordinator { navigationLogger.error("Failed to switch database: \(error.localizedDescription, privacy: .public)") AlertHelper.showErrorSheet( - title: String(localized: "Database Switch Failed"), + title: "\(PluginManager.shared.containerEntityName(for: connection.type)) Switch Failed", message: error.localizedDescription, window: NSApplication.shared.keyWindow ) diff --git a/TablePro/Views/Sidebar/SidebarView.swift b/TablePro/Views/Sidebar/SidebarView.swift index 1fe59a74d..dd05ef4d3 100644 --- a/TablePro/Views/Sidebar/SidebarView.swift +++ b/TablePro/Views/Sidebar/SidebarView.swift @@ -180,7 +180,8 @@ struct SidebarView: View { private var emptyState: some View { let entityName = PluginManager.shared.tableEntityName(for: viewModel.databaseType) let noItemsLabel = String(localized: "No \(entityName)") - let noItemsDetail = String(localized: "This database has no \(entityName.lowercased()) yet.") + let containerName = PluginManager.shared.containerEntityName(for: viewModel.databaseType).lowercased() + let noItemsDetail = "This \(containerName) has no \(entityName.lowercased()) yet." return VStack(spacing: 6) { Image(systemName: "tablecells") .font(.system(size: 28, weight: .thin)) diff --git a/TablePro/Views/Toolbar/TableProToolbarView.swift b/TablePro/Views/Toolbar/TableProToolbarView.swift index 8f9b90ba6..fa9dcf4f9 100644 --- a/TablePro/Views/Toolbar/TableProToolbarView.swift +++ b/TablePro/Views/Toolbar/TableProToolbarView.swift @@ -81,9 +81,9 @@ struct TableProToolbar: ViewModifier { Button { actions?.openDatabaseSwitcher() } label: { - Label("Database", systemImage: "cylinder") + Label(PluginManager.shared.containerEntityName(for: state.databaseType), systemImage: "cylinder") } - .help("Open Database (⌘K)") + .help("Open \(PluginManager.shared.containerEntityName(for: state.databaseType)) (⌘K)") .disabled( state.connectionState != .connected || PluginManager.shared.connectionMode(for: state.databaseType) == .fileBased) diff --git a/docs/newsletters/2026-03-29-v0.25-0.26.md b/docs/newsletters/2026-03-29-v0.25-0.26.md new file mode 100644 index 000000000..17581f2aa --- /dev/null +++ b/docs/newsletters/2026-03-29-v0.25-0.26.md @@ -0,0 +1,60 @@ +--- +subject: "SQL files, new filters, connection sharing, BigQuery - TablePro v0.25-0.26" +--- + +Two releases this week. Here's what's new. + +--- + +### SQL File Management + +Open, edit, and save .sql files directly. The file name shows in the title bar - same as any native Mac app. Cmd+S to save, Cmd+Shift+S to save as. Drag-and-drop works too. + +*[Screenshot: editor with a .sql file open, file name visible in the title bar]* + +--- + +### New Filter System + +Rebuilt from scratch with native macOS controls. 18 operators (contains, between, regex, IS NULL, etc.), raw SQL mode for custom WHERE clauses, and saved presets so you don't retype the same filters. + +Cmd+F to open. Apply with Enter. + +*[Screenshot: filter panel with 2-3 rows, one raw SQL and one column/operator/value]* + +--- + +### Connection Sharing + +Export connections as .tablepro files. Share them with your team - import preview shows duplicates and lets you pick what to keep. + +**Encrypted export (Pro):** Include passwords, protected by AES-256-GCM with a passphrase. + +**Linked Folders (Pro):** Point a folder at a shared drive. Connections inside appear read-only in the sidebar, always up to date. + +**Environment variables (Pro):** Use `$DB_HOST` or `${DB_PASSWORD}` in connection fields. Resolved at connect time. + +*[Screenshot: import preview dialog showing a list of connections with status badges]* + +--- + +### BigQuery Support + +Google BigQuery via the plugin registry. Connect with a service account JSON key or Application Default Credentials. Browse datasets, run SQL, export results. + +*[Screenshot: BigQuery query result with dataset sidebar]* + +--- + +### More in these releases + +- **Nested groups** - organize connections up to 3 levels deep +- **Column reorder** - drag columns in the Structure tab (MySQL/MariaDB) +- **AI kill switch** - one toggle to turn off all AI features +- **JSON viewer** - JSON fields in Row Details now display in a scrollable monospaced area +- **Safety dialogs** - confirmation prompts for deep links, imports, and startup scripts +- **Bug fixes** - MariaDB JSON hex dumps, MongoDB Atlas TLS, SSH profile sync, editor focus/scrolling, and more + +--- + +Update from the app (Sparkle auto-update) or download at [tablepro.app](https://tablepro.app).