Releases: TableProApp/TablePro
Releases · TableProApp/TablePro
Release list
v0.9.3
Fixed
- PostgreSQL SSH tunnel connections failing with "no encryption" due to SSL config not being preserved
v0.9.2
Fixed
- Fix app bundle not ad-hoc signed — signing step was unreachable when no dylibs were bundled
v0.9.1
Fixed
- Fix Sparkle auto-update failing with "improperly signed" error — release ZIPs now preserve framework symlinks and include proper ad-hoc code signatures
v0.9.0
Added
- Vim keybindings for SQL editor (Normal/Insert/Visual modes, motions, operators, :w/:q commands) with toggle in Editor Settings
^and_motions (first non-blank character) in Vim normal, visual, and operator-pending modes:qcommand to close current tab in Vim command-line mode- PostgreSQL schema switching via ⌘K database switcher (browse and switch between schemas like
public,auth, custom schemas)
Changed
- Convert QueryHistoryStorage and QueryHistoryManager from callback-based async dispatch to native Swift async/await — eliminates double thread hops per history operation
- Consolidate ExportService @published properties into single state struct — reduces objectWillChange events from 7 per batch to 1
- Consolidate ImportService @published properties into single state struct — reduces objectWillChange events during SQL import
- Replace DispatchQueue.main.asyncAfter chains in AppDelegate startup with structured Task-based retry loops
- Merge 3 identical Combine notification subscriptions in SidebarViewModel into Publishers.Merge3
- Make AIChatStorage encoder/decoder static — shared across all instances instead of duplicated
Fixed
- Cell edit showing modified background but displaying original value until save (reloadData during active editing ignored by NSTableView, updateNSView blocked by editedRow guard)
- Undo on inserted row cell edit not syncing insertedRowData (stale values after undo)
- Vim Escape key not exiting Insert/Visual mode when autocomplete popup is visible (popup's event monitor consumed the key)
- Copy (Cmd+C) and Cut (Cmd+X) not working in SQL editor — clipboard retained old value due to CodeEditTextView's copy: silently failing
- Vim yank/delete operations not syncing to system clipboard (register only stored text internally)
- Vim word motions (
w,b,e) using two-class word boundary detection instead of correct three-class (word chars, punctuation, whitespace) - Vim visual mode selection now correctly includes cursor character (inclusive selection matching real Vim behavior)
- Arrow keys now work in Vim visual/normal mode (mapped to h/j/k/l instead of bypassing the Vim engine)
- Vim block cursor now follows the moving end of the selection in visual mode instead of staying at the anchor
- Vim visual mode selection highlight now renders visibly (trigger needsDisplay after programmatic selection)
- Fix event monitor leaks in SQL editor —
deinitnow cleans up NSEvent monitors, notification observers, and work items that leaked when CodeEditSourceEditor never calleddestroy() - Fix unbounded memory growth from NativeTabRegistry holding full QueryTab objects (including RowBuffer references) — registry now stores lightweight TabSnapshot structs
- Fix SortedRowsCache storing full row copies — now stores index permutations only, halving sorted-tab memory
- Fix schema provider memory leak — shared providers are now reference-counted with 5s grace period removal when all windows for a connection close
- Fix duplicate schema fetches in InlineSuggestionManager — now shares the coordinator's SQLSchemaProvider instead of maintaining a separate cache
- Fix background tabs retaining full result data indefinitely — RowBuffer eviction frees memory for inactive tabs (re-fetched on switch back)
- Fix InMemoryRowProvider bulk cache eviction — now uses proximity-based eviction keeping entries near current scroll position
- Fix stale tabRowProviders entries when tab IDs change without count changing
- Fix crash on macOS 14.x caused by
_strchrnulsymbol not found in libpq.5.dylib — switch libpq and OpenSSL from dynamic Homebrew linking to vendored static libraries built with MACOSX_DEPLOYMENT_TARGET=14.0 - Fix duplicate tabs and lag when inserting SQL from AI Chat or History panel with multiple window-tabs open — notification handlers now only fire in the key window
- Fix "Run in New Tab" race condition in History panel — replaced fragile two-notification + 100ms delay pattern with a single atomic notification
- Fix MainContentCoordinator deinit Task that may never execute — added explicit teardown() method with didTeardown guard and orphaned schema provider purge
- Fix SQLEditorCoordinator deinit deferring InlineSuggestionManager cleanup to Task — added explicit destroy() lifecycle and didDestroy guard with warning log
- Fix ExportService while-true batch loops not checking Task.isCancelled — cancelled exports now stop promptly instead of running all remaining batches
- Fix DataGridView full column reconfiguration on every resultVersion bump — narrowed rebuild condition to only trigger when transitioning from empty state
- Fix ConnectionHealthMonitor fixed 30s interval that delays failure detection — added checkNow() with wakeUpContinuation for immediate health checks and exponential backoff
- Fix HistoryPanelView and TableStructureView asyncAfter copy-reset timers not cancellable — replaced with cancellable Task pattern
- Fix MainContentView redundant onChange handler causing cascading re-renders on tab/table changes
- Fix DatabaseManager notification observer creating unnecessary Tasks when self is already deallocated — added guard let self before Task creation
v0.8.0
New Features
- Native macOS Window Tabs: The tab bar is now rendered by macOS itself — identical to Finder, Safari, and Xcode tabs with automatic dark/light mode support, drag-to-reorder, and "Merge All Windows" for free
- Independent Tab Windows: Each tab is a full independent window with its own sidebar, editor, and state — no more shared state between tabs
Improvements
- Tab Switching Performance: Schema is now cached per connection so new native tabs reuse the already-loaded schema instead of re-fetching from the database (saves 500ms–2s per tab)
- Background Schema Loading: Schema loads concurrently — table data appears immediately while autocomplete schema loads in the background
- Sidebar MVVM Refactor: Sidebar table list migrated to a testable
SidebarViewModelarchitecture with extractedTableRowViewand context menu components - Window title updates dynamically after in-place navigation (sidebar click, FK navigation)
- 10+ SwiftUI rendering optimizations to prevent O(N) view cascades across windows
Bug Fixes
- Fixed sidebar losing keyboard focus (arrow key navigation) after opening a second table tab
- Fixed sidebar active state flash when clicking a table that opens in a new native window tab
- Fixed sidebar losing active state when opening a second table in a new native window tab
- Fixed sidebar not refreshing after switching databases via Cmd+K
- Fixed Cmd+W in empty state doing nothing — now closes the connection window and disconnects
- Fixed Cmd+K database switch flooding all windows with error alerts
- Fixed clicking a table in the sidebar replacing the current tab instead of opening a new one
- Fixed Cmd+W on any tab disconnecting the entire session
- Fixed Cmd+T from empty state creating two native tabs instead of one
- Fixed native tab title showing "SQL Query" instead of the table name
- Fixed Cmd+W on the last tab disconnecting the session instead of returning to empty state
v0.7.0
Added
- Quick search and filter rows can now be combined — when both are active, their WHERE conditions are joined with AND
- Foreign key columns now show a navigation arrow icon in each cell — click to open the referenced table filtered by the FK value
Changed
- Metadata queries (columns, FKs, row count) now run on a dedicated parallel connection, eliminating 200-300ms delay for FK arrows and pagination count on initial table load
- Approximate row count from database metadata displays instantly with data; exact count refines silently in the background
- Show warning indicator on filter presets referencing columns not in current table
- Increase filter row height estimate for better accessibility support
- FK navigation now uses dedicated FilterStateManager.setFKFilter API instead of direct property manipulation
- Add syntax highlighting to Import SQL file preview
- XLSX export now enforces the Excel row limit (1,048,576) per sheet and uses autoreleasepool per row to reduce peak memory during large exports
- Multiline cell values now use a scrollable overlay editor instead of the constrained field editor, enabling proper vertical scrolling and line navigation during inline editing
- AnyChangeManager now uses a reference-type box for lazy initialization, avoiding Combine pipeline creation during SwiftUI body evaluation
- DataGridView identity check moved before AppSettingsManager read to skip settings access when nothing has changed
- DataGridView async column width write-back now uses an isWritingColumnLayout guard to prevent two-frame bounce
- Tab switch flushPendingSave debounced to skip redundant saves within 100ms of rapid tab switching
- SQL editor frame-change notification throttled to 50ms to avoid redundant syntax highlight viewport recalculation on every keystroke
- SQL editor text binding sync now uses O(1) NSString length pre-check before O(n) full string equality comparison
- Toolbar executing state now fires a single objectWillChange instead of double-publishing isExecuting and connectionState
- Row provider onChange handlers coalesced into a single trigger to avoid redundant InMemoryRowProvider rebuilds
- SQL import now uses file-size estimation instead of a separate counting pass, eliminating the double-parse overhead for large files
- History cleanup COUNT + DELETE now wrapped in a single transaction to reduce journal flushes
- SQLite
fetchTableMetadatanow caps row count scan at 100k rows to avoid full table scans on large tables - SQLite
fetchIndexesuses table-valued pragma functions in a single query instead of N+1 separate PRAGMA calls - MySQL empty-result DESCRIBE fallback now only triggers for SELECT queries, avoiding redundant round-trips for non-SELECT statements
- Remove redundant
String(query)copy in MariaDB query execution - MySQL result fetching now uses
mysql_use_result(streaming) instead ofmysql_store_result(full buffering), so only the capped row count is held in memory instead of the entire server result set - Instant pagination via approximate row count — MySQL/PostgreSQL tables now show "~N rows" immediately with data, then refine to exact count in background
- QueryTab uses value-based equality for SwiftUI diffing, eliminating unnecessary ForEach re-renders on tab array writes
- Cached static regex for
extractTableName,SQLiteDriver.stripLimitOffset, and SQL function expressions to avoid per-call compilation - Static NumberFormatter in status bar to avoid per-render locale resolution
- Batch
TableProTabSmartfield writes into single array store to avoid 14 CoW copies per query execution - Tab persistence writes moved off main thread via
Task.detached - Single history entry per SQL import instead of per-statement recording
- WAL mode enabled for query history SQLite database
- Merged
fetchDatabaseMetadatainto single query for MySQL and PostgreSQL - Health ping now uses dedicated metadata driver to avoid blocking user queries
- SSH tunnel setup extracted into shared helper to eliminate code duplication
- PostgreSQL DDL queries restructured with
async letfor cleaner dispatch (sequential on serial connection queue) - Cancel query connection now uses 5-second connect timeout
- PostgreSQL connection parameters properly escaped for special characters
- SQLite
fetchAllColumnsoverridden with singlesqlite_master+pragma_table_infoquery - Eliminated intermediate
[UInt8]buffer in MySQL and PostgreSQL field extraction - Column layout sync gated behind user-resize flag to skip O(n) loop on cursor moves
- Column width calculation uses monospace character arithmetic instead of per-row CoreText calls
- DataChangeManager maintains change index incrementally instead of full O(n) rebuild
- JSON export buffers writes per row instead of per field
SQLFormatterServiceuses NSMutableString for keyword uppercasing and integer counter for placeholders- SQLContextAnalyzer uses single alternation regex and single-pass state machine for string/comment detection
escapeJSONStringiterates UTF-8 bytes instead of grapheme clustersAppSettingsStoragecaches JSONDecoder/JSONEncoder as stored propertiesAppSettingsManagerstores validated settings in memory after didSetFilterSettingsStorageuses tracked key set instead of loading full plist- Keychain saves use
SecItemAdd+SecItemUpdateupsert pattern instead of delete + add - Autocomplete
detectFunctionContextuses index tracking instead of character-by-character string building
Fixed
- Fix AND/OR filter logic mode ignored in query execution — preview showed correct OR logic but actual query always used AND
- Fix filter panel state (filters, visibility, quick search, logic mode) not preserved when switching between tabs
- Fix foreign key navigation filter being wiped when switching to a new tab (tab switch restore overwrote FK filter state)
- Fix pagination count appearing 200-300ms after data loads — approximate row count from database metadata now displays instantly with data, exact count refines silently in the background
- Fix foreign key navigation arrows and pagination count appearing with visible delay on initial table load — metadata now fetches on a dedicated parallel connection concurrent with the main query
- Fix LibPQ parameterized query using Swift
deallocate()forstrdup-allocated memory instead offree() - FTS5 search input now sanitized to prevent parse errors from special characters like *, OR, AND
- Fix SQL export corrupting newline/tab/backslash characters for PostgreSQL and SQLite (MySQL-style backslash escaping was incorrectly applied to all database types)
- Fix PostgreSQL SQL export failing to import when types/sequences already exist (
DROP IF EXISTSnow always emitted for dependent types and sequences) - Fix PostgreSQL SQL export missing
CREATE TYPEdefinitions for enum columns, causing import errors - Fix PostgreSQL DDL tab not showing enum type definitions used by table columns
- Fix compilation error for PostgreSQL dependent sequences export (
fetchDependentSequencesmissing fromDatabaseDriverprotocol) - Fix PostgreSQL LIKE/NOT LIKE expressions missing
ESCAPE '\'clause, causing wildcard escaping (\%,\_) to be treated as literal characters - Fix SQLite regex filter silently degrading to LIKE substring match instead of being excluded from the WHERE clause
v0.6.4
Fixed
- Fix PostgreSQL SQL export failing to fetch DDL for tables (passed quoted identifier instead of raw table name to catalog queries)
v0.6.3
Changed
- Extract shared
performDirectTabSwitchintoMainContentCoordinatorto eliminate duplicate tab-switch logic - Welcome window now uses native macOS frosted glass translucency (NSVisualEffectView with behind-window blending)
Fixed
- Auto-detect MySQL vs MariaDB server type from version string to use correct timeout variable (
max_execution_timefor MySQL,max_statement_timefor MariaDB) - Improved tab switching performance by caching row providers and change managers across SwiftUI render cycles
- Eliminated selection sync feedback loop causing redundant DataGridView updates during tab switch
- Enabled NSTableView row view recycling to reduce heap allocations during scrolling
- Reduced SwiftUI re-render cascades by batching @published mutations during tab switch
- Improved DataGrid scrolling performance:
- Row views now recycled via NSTableView's reuse pool instead of allocating new objects per scroll
- Replaced O(n) String.count with O(1) NSString.length for large cell value truncation
- Replaced expensive NSFontDescriptor.symbolicTraits checks with O(1) pointer equality on cached fonts
- Added layerContentsRedrawPolicy and canDrawSubviewsIntoLayer to reduce compositing overhead
- Cached NULL display string locally instead of per-cell singleton access
- Cached AnyChangeManager to avoid per-render allocation with Combine subscriptions
- Deferred accessibility label generation to when VoiceOver is active
- Removed unnecessary async dispatch in focusedColumn, collapsed two reloadData calls into one
v0.6.2
v0.6.0
Added
- Inline AI suggestions (ghost text) in the SQL editor — auto-triggers on typing pause, Tab to accept, Escape to dismiss
- Schema-aware inline suggestions — AI now uses actual table/column names from the connected database (cached with 30s TTL, respects
includeSchemaandmaxSchemaTablessettings) - AI feature highlight row on onboarding features page
- Added VoiceOver accessibility labels to custom controls: data grid (table view, column headers, cells), filter panel (logic toggle, presets, action buttons, filter row controls), toolbar buttons (connection switcher, database switcher, refresh, export, import, filter toggle, history toggle, inspector toggle), editor tab bar (tab items, close buttons, add tab button), and sidebar (table/view rows, search clear button)
Changed
- Migrated notification observers in
MainContentCommandActionsfrom Combine publishers (.publisher(for:).sink) to async sequences (for awaitoverNotificationCenter.default.notifications(named:)) — removesAnyCancellablestorage in favor ofTaskhandles with proper cancellation on deinit - Migrated tab state persistence from UserDefaults to file-based storage in Application Support — prevents large JSON payloads from bloating the plist loaded at app launch, with automatic one-time migration of existing data
- Refactored menu and toolbar commands from NotificationCenter to
@FocusedObjectpattern — menu commands and toolbar buttons now callMainContentCommandActionsmethods directly instead of posting global notifications, with context-aware routing for structure view operations - Redesigned connection form with tab-based layout (General / SSH Tunnel / SSL/TLS / Advanced), replacing the single-scroll layout
- Revamped connection form UI to use native macOS grouped form style (
Form/.formStyle(.grouped)) withLabeledContentfor automatic label-value alignment andSectionheaders — replacing the previous hand-rolledVStacklayout with customFormFieldcomponent - Removed unused
FormFieldcomponent and helper methods (iconForType,colorForType) - SQLite connections now only show General and Advanced tabs (SSH/SSL hidden)
- Added async/await wrapper methods to
QueryHistoryStorage— existing completion-handler API preserved for compatibility, newasyncoverloads usewithCheckedContinuationfor modern Swift concurrency callers
Fixed
- Fixed TOCTOU race condition in
SQLiteDriver— replacednonisolated(unsafe)+ DispatchQueue pattern with a dedicated actor (SQLiteConnectionActor) that serializes all sqlite3 handle access, preventing concurrent task races on the connection state - Consolidated multiple
.sheet(isPresented:)modifiers inMainContentViewinto a single.sheet(item:)with anActiveSheetenum — fixes SwiftUI anti-pattern where only the last.sheetmodifier reliably activates - Replaced blocking
Process.waitUntilExit()calls inSSHTunnelManagerwith asyncwithCheckedContinuation-based waiting, and replaced the fixed 1.5s sleep with active port probing — SSH tunnel setup no longer blocks the actor thread, keeping the UI responsive during connection - Eliminated potential deadlocks in
MariaDBConnectionandLibPQConnection— replaced allqueue.synccalls (indisconnect,deinit,isConnected,serverVersion) with lock-protected cached state andqueue.asynccleanup, preventing deadlocks when callbacks re-enter the connection queue - SQL editor now respects the macOS accessibility text size preference (System Settings > Accessibility > Display > Text Size) — the user's chosen font size is scaled by the system's preferred text size factor, with live updates when the setting changes
- Fixed retain cycle in
UpdaterBridge—.assign(to:on:self)retains self strongly; replaced with.sinkusing[weak self] - Fixed leaked NotificationCenter observer in
SQLEditorCoordinator— observer token is now stored and removed indestroy() - Eliminated tab switching delay — replaced view teardown/recreation with
ZStack+ForEachto keep NSViews alive, moved tab persistence I/O to background threads, skipped unnecessary change-tracking deep copies, and coalesced redundant inspector/sidebar updates during tab switch - Reduced tab-switch CPU spikes from 40-60% to ~10-20% by eliminating redundant
reloadData()calls:configureForTableno longer triggers a reload during tab switch (single controlled bump instead of 2-3),onChange(of: resultColumns)is suppressed while the switch is in progress, andDataGridView.updateNSViewskips all heavy work when the data identity hasn't changed - Table open now shows data instantly — split
executeQueryInternalinto two phases: rows display immediately after SELECT completes, metadata (columns, FKs, enums, row count) loads in the background without blocking the grid - Eliminated 20-80ms overhead when clicking an already-open table in the sidebar —
openTableTabshort-circuits immediately, andTableProTabSmartno longer fires@Publishedwhen the selected tab hasn't changed - Keychain
SecItemAddreturn values are now checked and logged — previously, failed writes (e.g.errSecDuplicateItem,errSecInteractionNotAllowed) were silently discarded, risking password loss - Added
kSecAttrServiceto all Keychain queries acrossConnectionStorage,LicenseStorage, andAIKeyStorage— items now have a proper service identifier, preventing potential collisions with other apps - Ensured proper cleanup for
@Statereference type tokens — tracked untrackedTaskinstances inImportDialog(file selection),AIProviderEditorSheet(model fetching, connection test), and addedonDisappearcancellation to prevent leaked work after view dismissal - Replaced
.onAppearwith.taskfor I/O operations inConnectionTagEditor— uses SwiftUI-idiomatic lifecycle-tied loading instead ofonAppearwhich can re-fire on navigation