DatabaseType is a string-based struct rather than an enum, so a type from a plugin the app has never heard of still round-trips through Codable.
The UI is SwiftUI with AppKit underneath it for windows, menus, and the data grid. Async work is Swift concurrency throughout. Database connectivity is native C libraries, linked as static .a files out of Libs/.
CLAUDE.md in the repository root is the authority on the rules a change has to satisfy: the invariants that have caused real bugs, the ABI policy, and what has to happen before a commit lands. This page is the map of where things live, not the rulebook.
Plugin system
PluginManager (Core/Plugins/) discovers bundles, version-checks them, and loads them. Four pieces carry the whole system:
Packages/TableProCore/Sources/TableProPluginKit is a symlink to the same files, so edit only the copy under Plugins/TableProPluginKit/.
Five drivers ship inside the app and cover nine databases. Another 17 come from the registry and
install on the first connection that needs them. See Plugins & Themes.
A driver is one of four roles a bundle can take. ExportFormatPlugin, ImportFormatPlugin, and DocumentInspectorPlugin are the others, which is how the CSV, JSON, SQL, XLSX, and MQL formats and the CSV inspector ship.
Opt-in protocols
A driver adopts these on top ofPluginDatabaseDriver to reach extra surfaces. The app finds them with a runtime cast, so skipping one costs nothing and no ABI bump is involved.
PluginKit ABI
TableProPluginKit builds withBUILD_LIBRARY_FOR_DISTRIBUTION = YES, so its public ABI is resilient: the Swift runtime fills any requirement an already-built plugin never implemented from the protocol’s default, and that plugin keeps loading under a newer app. Adding a requirement with a default is free. Removing one, or adding a parameter to an existing public initializer, breaks every shipped binary.
Run scripts/check-pluginkit-abi.sh before merging anything under Plugins/TableProPluginKit/. Plugin Development has the additive-versus-breaking rules and what a breaking change obliges you to do.
Key services
DatabaseManager (Core/Database/DatabaseManager.swift) is the connection pool and the interface every view and coordinator goes through for connect, disconnect, reconnect, and session tracking. ConnectionHealthMonitor pings each live connection every 30 seconds and reconnects with exponential backoff.
MainContentCoordinator is the central coordinator, one per connection session rather than per window, built by SessionStateFactory and split across extension files in Views/Main/Extensions/ (+Alerts, +Filtering, +Pagination, and the rest). New coordinator behavior goes in a new extension file rather than the main one.
Autocomplete is three pieces. CompletionEngine ranks the suggestions and knows nothing about the editor framework; QueryCompletionAdapter bridges it to CodeEdit. SQLContextAnalyzer reads what the cursor sits in: a table reference, a column reference, a keyword. SQLSchemaProvider is an actor that caches schema and hands concurrent callers the same in-flight Task rather than firing a second fetchTables().
Change tracking
1
Edit
DataChangeManager records the cell edit as a pending change. Nothing has reached the server.2
Save
SQLStatementGenerator turns the pending set into INSERT, UPDATE, and DELETE statements.3
Undo
Grid edits register on the window’s
UndoManager, which TabWindowController.windowWillReturnUndoManager resolves to the selected ConnectionWorkspace.undoManager. One window hosts several connections, so the undo stack is per connection, not per window. Structure edits use a private UndoManager inside StructureChangeManager.AnyChangeManager wraps whichever concrete manager is active behind the ChangeManaging protocol, so callers do not care which one they have.
MCP server
Core/MCP/ holds a Model Context Protocol server bound to loopback, speaking plain HTTP through NWListener. Nothing in it is reachable off the machine: no TLS, no remote mode. MCPProtocolDispatcher is an actor that resolves the protocol era from the request, checks scopes, runs each handler in its own task under a 330-second deadline, and normalizes the result envelope. MCPServerManager owns the lifecycle on the main actor: port binding, the bridge credential, the handshake file.
The wire contract, the tool catalog, and the version policy are documented for consumers: MCP Protocol, MCP Tools, Versioning.
Connecting
A cancelled connect is the hard case here, becauseTask.cancel() cannot interrupt a driver blocked in a C call. CLAUDE.md sets out what a driver owes the app on that path, written down after the same bug shipped four times.
State and storage
CLAUDE.md carries the full storage table, including which stores sync through iCloud and which stay device-local.
Dependencies
Plugins/DamengDriverPlugin links a Rust wire-protocol bridge built from Native/DamengBridge, not an SPM package.
For the repository layout, see Project Structure.
