This file covers the macOS and iOS apps in apple/, and every command in it runs from apple/. AGENTS.md holds the invariants and the rules every change meets; where this file and it disagree, it wins. What both apps share, branch naming and pull requests among it, is in the root CONTRIBUTING.md. The docs site lives in TableProApp/docs, and releasing is in RELEASING.md. The Linux app has its own setup and rules in linux/CONTRIBUTING.md.
| Software | Version | Notes |
|---|---|---|
| Xcode | 27.1 | What CI selects in .github/actions/setup-xcode. Code that needs the macOS 27 SDK sits behind SDK_MACOS_27 (Configs/Base.xcconfig) so Xcode 26 can still build, though nothing on CI builds with 26. Symbols newer than the deployment target (AGENTS.md) sit behind #available |
| XcodeGen | 2.46.0 exactly | The generated project differs between versions, so scripts/generate-project.sh warns on any other |
| SwiftLint | 0.65.1 | The version CI runs |
| GitHub CLI | any | Optional: scripts/download-libs.sh falls back to curl |
| Go | toolchain in Native/HanaBridge/go.mod |
SAP HANA plugin only: run scripts/build-hana.sh with no argument before building HanaDriver or AllPlugins. When the installed go is another release, go switches to that toolchain itself |
| Rust | Native/DamengBridge/rust-toolchain.toml |
Dameng plugin only, through rustup, which installs it: run scripts/build-dameng.sh arm64 before building DamengDriver or AllPlugins |
Fork the repo on GitHub, then:
git clone https://github.com/<your-fork>/TablePro.git && cd TablePro/apple
brew install xcodegen swiftlint
scripts/download-libs.sh
cp Configs/Secrets.xcconfig.example Configs/Secrets.xcconfig
scripts/generate-project.sh
open TablePro.xcodeprojSelect the TablePro scheme and My Mac, then press Cmd+R.
scripts/download-libs.sh fetches two archives from the libs-<n> GitHub release that Libs/release names: the macOS .a files (libmariadb, libpq, libduckdb, libmongoc and the rest) and the iOS xcframeworks. None of it is in git, so a build that skips this step fails at link time. Every run, including one that downloads nothing, verifies Libs/ against the checksums committed at HEAD. Publishing a rebuilt library is in RELEASING.md, Static libraries.
Edit Configs/Secrets.xcconfig. The template ships the literal YOUR_TEAM_ID, which fails to sign the same way a missing file does:
TABLEPRO_DEVELOPMENT_TEAM = <your team id>
TABLEPRO_APP_BUNDLE_IDENTIFIER = com.<yourhandle>.TablePro
A free personal team works. Debug builds sign against TablePro/TablePro.Debug.entitlements, which drops iCloud, so sync turns itself off. Plugin bundles sign with the same team, and the MCP helper signs ad hoc. The file is gitignored, so these settings never reach a commit and survive regenerating the project. Signing changed in the Xcode UI does not: the next scripts/generate-project.sh discards it. To check it, save a connection password, relaunch, and reopen the connection: the password is still there.
xcodebuild -project TablePro.xcodeproj -scheme TablePro -configuration Debug build -skipPackagePluginValidationXcode and the command line both write the app to ~/Library/Developer/Xcode/DerivedData/TablePro-*/Build/Products/Debug/TablePro.app. When to regenerate the project, and why never to pass SWIFT_VERSION=: AGENTS.md, Commands and Repository map. Debug builds sign with the team from TABLEPRO_DEVELOPMENT_TEAM, and a plugin in the user plugins folder must carry the same team as the running app.
The app scheme compiles only the plugins the app embeds. The AllPlugins aggregate compiles all of them; when to run it is in .claude/rules/plugin-system.md, Building.
xcodebuild build -project TablePro.xcodeproj -scheme AllPlugins -destination 'platform=macOS' -skipPackagePluginValidation CODE_SIGNING_ALLOWED=NOscripts/install-plugin-dev.sh <Target> copies a registry plugin you built in Debug into the user plugins folder.
scripts/release/build.sh builds the app and the registry plugins the way a tag does, universal, into build/release/; package.sh writes the assets to dist/. Release Build runs the same:
export VERSION=0.0.0-dev BUILD=9999999
scripts/release/build.sh app --unsigned && scripts/release/package.sh app --unsigned
scripts/build-dameng.sh both && scripts/build-hana.sh both
scripts/release/build.sh plugins --unsigned && scripts/release/package.sh plugins --unsignedSigned: drop --unsigned, with the Developer ID identity and the provisioning profile scripts/release/ExportOptions.plist names. scripts/release/notarize.sh <path>... needs the profile xcrun notarytool store-credentials TablePro --key <p8> --key-id <id> --issuer <issuer> creates.
| Reach for | When |
|---|---|
Cmd+Shift+K in Xcode |
The usual case |
xcodebuild -project TablePro.xcodeproj -scheme TablePro clean |
Same, from a terminal |
rm -rf ~/Library/Developer/Xcode/DerivedData |
Package resolution is stuck or a stale module keeps coming back. Clears every project on the machine |
From the repository root, prefer .claude/skills/fix-issue/scripts/verify.sh test <Suite> (AGENTS.md, Commands): it finds the target that declares the suite and fails a run that executed nothing. Where a test goes and how to write one: .claude/rules/tests.md; UI flows: .claude/rules/ui-tests.md.
The raw equivalents, in order: a suite in the Unit plan, a UI flow (needs Automation Mode: .claude/skills/fix-issue/references/verification-apple.md, INCONCLUSIVE), a TableProCore or TableProOracle suite (no Xcode plan lists those two packages), and the iOS app:
xcodebuild -project TablePro.xcodeproj -scheme TablePro test -testPlan Unit -skipPackagePluginValidation -only-testing:<Target>/<Suite>
xcodebuild -project TablePro.xcodeproj -scheme TablePro test -testPlan UI -skipPackagePluginValidation -only-testing:TableProUITests/<Suite>
swift test --package-path Packages/TableProCore --filter '<Target>.<Suite>'
xcodebuild -project TableProMobile/TableProMobile.xcodeproj -scheme TableProMobile test -only-testing:TableProMobileTests/<Suite> \
-destination 'platform=iOS Simulator,name=<iPhone>' -parallel-testing-enabled NO -skipPackagePluginValidation CODE_SIGNING_ALLOWED=NO ARCHS=arm64scripts/benchmark-app-paths.sh times cold launch, opening a 1,000,000-row table and switching tabs, 10 runs each, and checks the median and 95th percentile against the budgets in scripts/benchmark/app-paths/baseline.json. Usage, permissions and the fixtures are in its header. In Instruments the same three paths are the AppLaunch, TableLoad and TabSwitch intervals under Points of Interest.
scripts/benchmark-large-catalog.sh times the sidebar, Open Quickly and autocomplete on 50,000 tables (scripts/probes/build-large-catalog-fixtures.sh builds the fixtures) against scripts/benchmark/large-catalog/baseline.json, and fails any step during which the main thread stalls for more than 100 ms. TABLEPRO_INTERACTION_TRACE=1 prints each traced step and each stall of 50 ms or more on standard error; in Instruments the steps are the Interaction intervals.
.swiftlint.yml is the source of truth. Run swiftlint lint --strict <changed .swift files>; CI lints all of apple/ the same way on every pull request, and nothing lints during an Xcode build. Formatting the linter leaves to you:
| Rule | Value |
|---|---|
| Indentation | 4 spaces |
| Line length | Wrap at 120 |
| Braces | K&R |
| Wrapped arguments | One per line |
| Trailing commas | None |
self. |
Only where the compiler needs it |
Logging, storage and localization: AGENTS.md, Invariants that apply everywhere and Every change.
The architecture is in the area rules under .claude/rules/, and its invariants in AGENTS.md, Invariants that apply everywhere.
| Dependency | Source | Purpose |
|---|---|---|
| TableProEditor | Packages/, vendored from CodeEditSourceEditor and CodeEditTextView |
The SQL editor |
| TableProGrammars | Packages/ |
Tree-sitter grammars |
| TableProCore | Packages/ |
Modules the macOS and iOS apps share |
| TableProMacCore | Packages/ |
macOS-only modules the app links: utilities, SSH config, MCP |
| TableProOracle | Packages/, wraps a pinned fork of OracleNIO |
Oracle wire protocol |
| Sparkle | SPM | Updates, EdDSA-signed |
| Yams | SPM | YAML for project folder import |
| swift-nio, swift-nio-ssl, zstd | SPM | The Kafka plugin's transport, TLS and zstd codec |
A new engine ships as a registry plugin: a .tableplugin bundle under Plugins/ that implements DriverPlugin and PluginDatabaseDriver from TableProPluginKit, with a target in project.yml. The checklist is in .claude/rules/plugin-system.md, Structure. Guides: Plugin Development and Plugin Registry.
Translate through Xcode's export and import, one XLIFF file per language. Never edit Localizable.xcstrings by hand: Xcode is the only tool that writes it, so a translation diff holds your language and nothing else.
xcodebuild -exportLocalizations -project TablePro.xcodeproj -scheme TablePro \
-localizationPath Localization -exportLanguage fr
# translate Localization/fr.xcloc in Xcode, or its Localized Contents/fr.xliff in any XLIFF editor
xcodebuild -importLocalizations -project TablePro.xcodeproj -localizationPath Localization/fr.xcloc
scripts/localization.py status # what is translated, per languageThe export builds the app first, so run scripts/download-libs.sh and scripts/generate-project.sh once before it. Pass an existing language code to fill gaps, or a new one to start a language. Commit the catalog, not the export: Localization/ is ignored. The iPhone and iPad app has its own catalog: run the same commands on TableProMobile/TableProMobile.xcodeproj with the TableProMobile scheme. Plugin and package strings, and strings a command-line build added: AGENTS.md, Every change 3.
| Symptom | Fix |
|---|---|
xcodegen: command not found |
brew install xcodegen, then check xcodegen --version reports 2.46.0 |
TablePro.xcodeproj does not exist |
Run scripts/generate-project.sh |
| A new source file does not compile | Run scripts/generate-project.sh |
Undefined symbols for architecture arm64 |
Libs/ is empty or stale: scripts/download-libs.sh, or --force to overwrite |
Libs does not match the checksums committed in git. |
Re-run scripts/download-libs.sh --force. A library rebuilt on purpose is published as RELEASING.md describes |
error: …/tablepro-hana-helper is missing or has no <arch> slice |
Run scripts/build-hana.sh with no argument, which builds both architectures |
| Signing errors after a fresh clone | Configs/Base.xcconfig defaults to the maintainer's team and includes Secrets.xcconfig optionally, so a build without it still runs and fails to sign. Create the file with your team |
| Package resolution fails | Cmd+Shift+K and reopen Xcode; then rm -rf ~/Library/Developer/Xcode/DerivedData |
Test runs left defaults domains (com.TablePro.tests.*, com.TablePro.uitest.*) or temp folders behind |
Quit TablePro, then run scripts/clean-test-residue.sh. It only reports until you pass --apply |