Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ Thumbs.db
*.p12
*.mobileprovision
Secrets.xcconfig
Local.xcconfig

# Debug
*.log
Expand Down Expand Up @@ -154,3 +155,4 @@ fix-1322-plugin-abi-and-registry-overhaul.diff

# Issue analysis blueprints (local only)
.analysis/
.docs/
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- BigQuery: the sidebar now shows every dataset as an expandable node, with each dataset's tables loading when you open it, instead of showing one dataset at a time behind a picker.
- Mark a table as a favorite by clicking the star button at the end of its sidebar row. Favorites are pinned to the top of their section, appear in a dedicated Tables group in the Favorites tab, and sync through iCloud.
- A plus button next to the sidebar filter creates a new table without right-clicking. The button is disabled while safe mode blocks writes.
- 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. (#1352)
- OpenCode Zen as an AI provider. Add it from the provider list and paste an OpenCode key, or leave the key blank to use the free models; the model list loads automatically, covering the Claude, GPT, Gemini, and open models Zen serves. (#1400)
- Oracle Database 11g (11.1 and 11.2) now connects. Previously only 12c and later worked, so 11g servers failed with a "Server Version Not Supported" error. (#1425)
- Oracle connections can now use a SID instead of a service name. Set Connection Type to SID in the connection form and enter the SID. (#1425)
Expand All @@ -20,6 +23,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Clearing a query with the trash button now also clears its results, and a new Clear Results item on the results right-click menu clears results on their own. (#1256)
- Inserting SQL from AI Chat opens it in a new query tab instead of appending to the current query. An empty editor is filled in place. (#1257)

### Changed

- 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 next to the sidebar filter instead.

### Fixed

- Pasting copied rows no longer misplaces values when a cell contains a comma (such as a user agent string); each value stays in its own column, and a real NULL is kept distinct from the literal text "NULL".
Expand Down
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ These govern every decision — code, architecture, tooling, and process:
4. **Clean code** — self-explanatory naming, early returns over nested conditionals, small focused functions. No comments in the codebase — code must be self-documenting through clear naming and structure.
5. **Root cause fixes** — don't patch symptoms. Diagnose the underlying issue, add logging to debug if needed, then fix the actual cause.
6. **No hacky solutions** — no backward-compatibility shims, no temporary workarounds left in place, no duct tape. If the right fix is harder, do the right fix.
7. **Testability** — if a feature is testable, write tests. When tests fail, fix the source code — never adjust tests to match incorrect output.
7. **Testability** — every testable code change needs unit/function tests, and UI/user-flow changes need UI automation when deterministic. When tests fail, fix the source code — never adjust tests to match incorrect output.
8. **Maintainability** — follow existing patterns but offer refactors when they improve quality. Extract into extensions when approaching size limits. Group by domain logic.
9. **Scalability** — design for the plugin system's open-ended nature. `DatabaseType` is a struct, not an enum. All switches need `default:`.

Expand Down Expand Up @@ -52,6 +52,7 @@ swiftformat . # Format code
xcodebuild -project TablePro.xcodeproj -scheme TablePro test -skipPackagePluginValidation
xcodebuild -project TablePro.xcodeproj -scheme TablePro test -skipPackagePluginValidation -only-testing:TableProTests/TestClassName
xcodebuild -project TablePro.xcodeproj -scheme TablePro test -skipPackagePluginValidation -only-testing:TableProTests/TestClassName/testMethodName
xcodebuild -project TablePro.xcodeproj -scheme TablePro test -skipPackagePluginValidation -only-testing:TableProUITests

# DMG
scripts/create-dmg.sh
Expand Down Expand Up @@ -168,6 +169,7 @@ Missing a case produces a wrong "{Language} Query" title on the first frame.
| Tab state | JSON persistence | `TabPersistenceService` / `TabStateStorage` |
| Filter presets | UserDefaults | `FilterSettingsStorage` |
| Per-table filters | UserDefaults | `FilterSettingsStorage` (saves `appliedFilters` only) |
| Favorite tables | UserDefaults | `FavoriteTablesStorage` (global, by table name) |

### Logging & Debugging

Expand Down Expand Up @@ -222,7 +224,7 @@ These are **non-negotiable** — never skip them:
- Settings changes → `docs/customization/settings.mdx`
- Database driver changes → `docs/databases/*.mdx`

4. **Tests**: Write tests for testable features. When tests fail, fix the source code — never adjust tests to match incorrect output. Tests define expected behavior.
4. **Tests**: Every code change must include or update unit/function tests for testable behavior. UI and user-flow changes must also include or update `TableProUITests` UI automation when the flow can run deterministically; if not, state the blocker in the handoff. When tests fail, fix the source code — never adjust tests to match incorrect output. Tests define expected behavior.

5. **Lint after changes**: Run `swiftlint lint --strict` to verify compliance.

Expand Down
118 changes: 118 additions & 0 deletions TablePro.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,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 @@ -297,6 +304,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 @@ -677,6 +685,11 @@
path = TableProTests;
sourceTree = "<group>";
};
5AF00A122FB9000000000001 /* TableProUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = TableProUITests;
sourceTree = "<group>";
};
5AE4F4812F6BC0640097AC5B /* Plugins/CloudflareD1DriverPlugin */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
Expand Down Expand Up @@ -708,6 +721,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
5AF00A132FB9000000000001 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
5A3BE6F52F97DA8100611C1F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -939,6 +959,7 @@
5A86E000500000000 /* Plugins/MQLExportPlugin */,
5A86F000500000000 /* Plugins/SQLImportPlugin */,
5ABCC5A82F43856700EAF3FC /* TableProTests */,
5AF00A122FB9000000000001 /* TableProUITests */,
5A32BC012F9D5F1300BAEB5F /* mcp-server */,
5A1091C82EF17EDC0055EA7C /* Products */,
5A05FBC72F3EDF7500819CD7 /* Recovered References */,
Expand Down Expand Up @@ -968,6 +989,7 @@
5A86E000100000000 /* MQLExport.tableplugin */,
5A86F000100000000 /* SQLImport.tableplugin */,
5ABCC5A72F43856700EAF3FC /* TableProTests.xctest */,
5AF00A102FB9000000000001 /* TableProUITests.xctest */,
5AEA8B2A2F6808270040461A /* EtcdDriverPlugin.tableplugin */,
5ADDB00300000000000000A0 /* DynamoDBDriverPlugin.tableplugin */,
5ABQR00300000000000000A0 /* BigQueryDriverPlugin.tableplugin */,
Expand Down Expand Up @@ -1524,6 +1546,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 @@ -1671,6 +1714,10 @@
CreatedOnToolsVersion = 26.2;
TestTargetID = 5A1091C62EF17EDC0055EA7C;
};
5AF00A142FB9000000000001 = {
CreatedOnToolsVersion = 26.5;
TestTargetID = 5A1091C62EF17EDC0055EA7C;
};
5AE4F4732F6BC0640097AC5B = {
CreatedOnToolsVersion = 26.3;
LastSwiftMigration = 2630;
Expand Down Expand Up @@ -1725,6 +1772,7 @@
5A86E000000000000 /* MQLExport */,
5A86F000000000000 /* SQLImport */,
5ABCC5A62F43856700EAF3FC /* TableProTests */,
5AF00A142FB9000000000001 /* TableProUITests */,
5AEA8B292F6808270040461A /* EtcdDriverPlugin */,
5AE4F4732F6BC0640097AC5B /* CloudflareD1DriverPlugin */,
5ADDB00600000000000000B0 /* DynamoDBDriverPlugin */,
Expand All @@ -1744,6 +1792,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
5AF00A162FB9000000000001 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
5A3BE6F62F97DA8100611C1F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -1929,6 +1984,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
5AF00A152FB9000000000001 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
5A3BE6F42F97DA8100611C1F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -2212,6 +2274,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 @@ -3713,6 +3780,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 @@ -4116,6 +4225,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
11 changes: 11 additions & 0 deletions TablePro.xcodeproj/xcshareddata/xcschemes/TablePro.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@
ReferencedContainer = "container:TablePro.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5AF00A142FB9000000000001"
BuildableName = "TableProUITests.xctest"
BlueprintName = "TableProUITests"
ReferencedContainer = "container:TablePro.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
2 changes: 2 additions & 0 deletions TablePro/Core/Services/AppServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct AppServices {
let schemaService: SchemaService
let schemaProviderRegistry: SchemaProviderRegistry
let sqlFavoriteManager: SQLFavoriteManager
let favoriteTablesStorage: FavoriteTablesStorage
let aiChatStorage: AIChatStorage
let aiKeyStorage: AIKeyStorage
let groupStorage: GroupStorage
Expand Down Expand Up @@ -43,6 +44,7 @@ struct AppServices {
schemaService: .shared,
schemaProviderRegistry: .shared,
sqlFavoriteManager: .shared,
favoriteTablesStorage: .shared,
aiChatStorage: .shared,
aiKeyStorage: .shared,
groupStorage: .shared,
Expand Down
Loading
Loading