Skip to content

Commit f31b908

Browse files
committed
fix(landing): derive the engine count from the grid, and correct it to 29
The site published "25" in seven rendered places and about forty fields of comparisons.json. It was not an arbitrary string: it counted distinct driverGroup values in database-grid.json, while the headline above it promised databases. Two quantities wearing one number, so raising either broke the other. It had also gone stale everywhere at once — the app registered 28 types, the docs said 27, the site said 25 — without a single failing test, because every copy agreed with every other copy and none agreed with the app. Diffing allRegisteredTypeIds() against the grid found the gap was real content, not phrasing: Dameng and Kafka ship as driver plugins and had no tile, no icon and no page, and libSQL and Turso shared one tile while the app registers them separately. Flipping the literals alone would have put "29 databases." above a grid of 26 countable tiles. ENGINE_COUNT now derives from database-grid.json, which is what a reader can actually count on the page, so the claim and the grid cannot drift apart again. DRIVER_PLUGIN_COUNT stays a literal because nothing here can derive it.
1 parent b92d5f9 commit f31b908

14 files changed

Lines changed: 419 additions & 87 deletions

resources/blog/open-source-db-clients-2026.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Status: actively maintained. Used by enterprise data teams that need schema docu
7676

7777
### TablePro (AGPLv3)
7878

79-
Native macOS app written in Swift and AppKit. 25 databases through a plugin architecture (MySQL, Postgres, MongoDB, Redis, ClickHouse, BigQuery, DynamoDB, Cloudflare D1, Turso, etc.). Free, open source, with paid features (license server, advanced AI usage).
79+
Native macOS app written in Swift and AppKit. 28 databases through a plugin architecture (MySQL, Postgres, MongoDB, Redis, ClickHouse, BigQuery, DynamoDB, Cloudflare D1, Turso, etc.). Free, open source, with paid features (license server, advanced AI usage).
8080

8181
Best at: native macOS performance with broad database coverage. ~80 MB at idle, ~1 second cold start. AI assistant in the free tier. iOS app with iCloud sync.
8282

resources/data/comparisons.json

Lines changed: 42 additions & 42 deletions
Large diffs are not rendered by default.

resources/data/database-grid.json

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@
119119
"category": "relational",
120120
"href": "/oracle-client"
121121
},
122+
{
123+
"name": "Dameng",
124+
"icon": null,
125+
"monogram": "DM",
126+
"port": "5236",
127+
"distribution": "plugin",
128+
"driverGroup": "dameng",
129+
"category": "relational",
130+
"href": null
131+
},
122132
{
123133
"name": "DuckDB",
124134
"icon": "/images/databases/duckdb.svg",
@@ -179,6 +189,16 @@
179189
"category": "document-kv",
180190
"href": "/surrealdb-client"
181191
},
192+
{
193+
"name": "Kafka",
194+
"icon": null,
195+
"monogram": "KA",
196+
"port": "9092",
197+
"distribution": "plugin",
198+
"driverGroup": "kafka",
199+
"category": "document-kv",
200+
"href": null
201+
},
182202
{
183203
"name": "Cloudflare D1",
184204
"icon": "/images/databases/cloudflare-d1.svg",
@@ -220,15 +240,25 @@
220240
"href": "/snowflake-client"
221241
},
222242
{
223-
"name": "libSQL / Turso",
224-
"icon": "/images/databases/turso.svg",
243+
"name": "libSQL",
244+
"icon": null,
225245
"monogram": "LS",
226246
"port": "API",
227247
"distribution": "plugin",
228248
"driverGroup": "libsql",
229249
"category": "file-embedded",
230250
"href": "/turso-client"
231251
},
252+
{
253+
"name": "Turso",
254+
"icon": "/images/databases/turso.svg",
255+
"monogram": "TU",
256+
"port": "API",
257+
"distribution": "plugin",
258+
"driverGroup": "libsql",
259+
"category": "file-embedded",
260+
"href": "/turso-client"
261+
},
232262
{
233263
"name": "Beancount",
234264
"icon": "/images/databases/beancount.svg",

resources/js/components/landing/database-grid.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import DatabaseMark from '@/components/ui/database-mark';
88
import SectionShell from '@/components/ui/section-shell';
99
import { CELL_DENSITY, cellBorders, GridCell, type ColumnMap } from '@/components/ui/grid-cell';
1010
import { GITHUB_REPO_URL } from '@/data/links';
11+
import { BUNDLED_ENGINE_COUNT, ENGINE_COUNT, ON_DEMAND_ENGINE_COUNT } from '@/data/engines';
1112

1213
interface DatabaseTile {
1314
name: string;
@@ -182,9 +183,9 @@ export default function DatabaseGrid() {
182183
tone="raised"
183184
id="databases"
184185
label="Databases"
185-
headline="25 databases."
186+
headline={`${ENGINE_COUNT} databases.`}
186187
headlineMuted="One native driver each. No JDBC."
187-
lede="Nine drivers ship inside the app. The other sixteen download the first time you pick one, signature verified, with no restart."
188+
lede={`${BUNDLED_ENGINE_COUNT} drivers ship inside the app. The other ${ON_DEMAND_ENGINE_COUNT} download the first time you pick one, signature verified, with no restart.`}
188189
>
189190
<FullLine />
190191
<Container>

resources/js/components/landing/hero.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Button from '@/components/ui/button';
77
import { AppleGlyph } from '@/components/ui/glyph';
88
import { trackDownload } from '@/lib/analytics';
99
import { GITHUB_REPO_URL } from '@/data/links';
10+
import { ENGINE_COUNT } from '@/data/engines';
1011

1112
interface Props {
1213
githubStars?: number | null;
@@ -89,7 +90,7 @@ export default function Hero({ githubStars, latestRelease }: Props) {
8990
* sections below read as evidence for a claim already made.
9091
*/}
9192
<p className="max-w-[58ch] px-4 py-5 text-lg text-muted-foreground text-pretty sm:py-6">
92-
TablePro is a free, open source database client for macOS. 25 engines through native drivers, and
93+
TablePro is a free, open source database client for macOS. {ENGINE_COUNT} engines through native drivers, and
9394
nothing runs against your database that you have not read first.
9495
</p>
9596
<FullLine />

resources/js/components/landing/spec-strip.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import DataTable, { TABLE_COLUMN_RULE, TABLE_ROW_RULE } from '@/components/ui/da
44
import FootNote from '@/components/ui/footnote';
55
import { FullLine } from '@/components/ui/full-line';
66
import { GITHUB_REPO_URL } from '@/data/links';
7+
import { BUNDLED_ENGINE_COUNT, ENGINE_COUNT, ON_DEMAND_ENGINE_COUNT } from '@/data/engines';
78

89
interface Props {
910
latestRelease?: { version: string | null; publishedAt: string | null; countLast30Days: number | null } | null;
@@ -55,7 +56,7 @@ function formatReleaseDate(iso: string): string {
5556
*/
5657
export default function SpecStrip({ latestRelease, downloads }: Props) {
5758
const specs: Spec[] = [
58-
{ label: 'databases', value: '25', sub: '9 bundled, 16 on demand', numeric: true },
59+
{ label: 'databases', value: String(ENGINE_COUNT), sub: `${BUNDLED_ENGINE_COUNT} bundled, ${ON_DEMAND_ENGINE_COUNT} on demand`, numeric: true },
5960
{ label: 'cold_start', value: 'Under 1s', sub: 'Cold, to first window', numeric: true },
6061
{ label: 'idle_rss', value: '~80 MB', sub: 'One connection, open and idle', numeric: true },
6162
downloads?.total

resources/js/data/engines.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import gridData from '../../data/database-grid.json';
2+
3+
/**
4+
* Every engine-count claim on the site, derived from one dataset.
5+
*
6+
* The figure was written out as "25" in eight places — the hero, the grid
7+
* headline and its lede, the spec strip, the licence block, an FAQ answer, the
8+
* JSON-LD publisher blurb and two fields on Home.tsx — plus roughly thirty more
9+
* across `comparisons.json`. Three of those were stale in a way nobody could
10+
* see from the source: the app had reached 28 registered types while the site
11+
* still said 25 and the docs said 27.
12+
*
13+
* The trap in the old copy was that "25" was not arbitrary. It counted distinct
14+
* `driverGroup` values in `database-grid.json`, which is a *driver* count, while
15+
* the headline above it promised *databases*. Two different quantities wearing
16+
* the same number, so raising one silently broke the other.
17+
*
18+
* These derive from the grid instead. The grid is what a reader can actually
19+
* count on the page, so a claim that disagrees with it is a claim the page
20+
* disproves two rows down. `EngineCountTest` pins the derived value, so adding
21+
* a tile is a deliberate change to the marketing copy rather than a side effect.
22+
*/
23+
interface DatabaseTile {
24+
distribution: 'builtin' | 'plugin';
25+
}
26+
27+
const tiles = gridData as DatabaseTile[];
28+
29+
/**
30+
* Engines a connection can be opened against — the app's registered type ids,
31+
* one grid tile each.
32+
*
33+
* Turso was the open question and is now counted. It had its own `DatabaseType`
34+
* and its own `/turso-client` page but no snapshot, resolving through
35+
* `reverseTypeIndex["Turso"] = "libSQL"`, so it shared a `libSQL / Turso` tile
36+
* and the app returned 28. It now follows the ScyllaDB precedent — an alias that
37+
* also carries an entry of its own — so libSQL and Turso are two tiles over one
38+
* driver, exactly as Cassandra and ScyllaDB are.
39+
*/
40+
export const ENGINE_COUNT: number = tiles.length;
41+
42+
/** Drivers compiled into the app, ready on first launch. */
43+
export const BUNDLED_ENGINE_COUNT: number = tiles.filter((tile) => tile.distribution === 'builtin').length;
44+
45+
/** The rest, fetched and signature-verified the first time one is picked. */
46+
export const ON_DEMAND_ENGINE_COUNT: number = ENGINE_COUNT - BUNDLED_ENGINE_COUNT;
47+
48+
/**
49+
* Driver plugin targets in the app repository, which is fewer than the engines
50+
* they serve: PostgreSQL also drives Redshift, CockroachDB and PGlite, Cassandra
51+
* drives ScyllaDB, and libSQL drives Turso.
52+
*
53+
* Nothing in this repository can derive this — it is a count of build targets in
54+
* an application this site cannot see — so it is a literal, and the only one.
55+
*/
56+
export const DRIVER_PLUGIN_COUNT = 23;

resources/js/data/faqs.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DRIVER_PLUGIN_COUNT, ENGINE_COUNT } from '@/data/engines';
2+
13
export interface FaqItem {
24
question: string;
35
answer: string;
@@ -23,7 +25,7 @@ export interface FaqItem {
2325
export const faqs: FaqItem[] = [
2426
{
2527
question: 'Is it really free, or free for now?',
26-
answer: 'Free, permanently. TablePro is AGPLv3 and the whole app works without a license, with no trial countdown and no per-Mac limit. All 25 databases, the SQL editor, the data grid, the AI assistant, the MCP server, Safe Mode with Touch ID, SSH tunnels, ER diagrams and XLSX export cost nothing. Starter adds seven: Compare & Sync, Query Insights, result charts, iCloud Sync, Linked Folders, encrypted connection export, and environment variables in connection fields. Team adds two more, the Team Catalog and the Team Library.',
28+
answer: `Free, permanently. TablePro is AGPLv3 and the whole app works without a license, with no trial countdown and no per-Mac limit. All ${ENGINE_COUNT} databases, the SQL editor, the data grid, the AI assistant, the MCP server, Safe Mode with Touch ID, SSH tunnels, ER diagrams and XLSX export cost nothing. Starter adds seven: Compare & Sync, Query Insights, result charts, iCloud Sync, Linked Folders, encrypted connection export, and environment variables in connection fields. Team adds two more, the Team Catalog and the Team Library.`,
2729
},
2830
{
2931
question: 'Can I use TablePro at work under AGPLv3?',
@@ -42,8 +44,14 @@ export const faqs: FaqItem[] = [
4244
answer: 'In the macOS Keychain. Connection details live in a plain JSON file with no secrets in it. A connection can also skip storage entirely and resolve its password at connect time from a file, an environment variable, a shell command, 1Password, HashiCorp Vault or AWS Secrets Manager.',
4345
},
4446
{
45-
question: 'Why do Cassandra and ScyllaDB appear separately when you say 25?',
46-
answer: 'The grid shows 26 tiles because Cassandra and ScyllaDB are two entries in the connection chooser. They share one driver, which is why the driver count is 25.',
47+
/*
48+
* This used to reconcile 26 tiles against a claim of 25, because the
49+
* headline quoted driver groups while the grid rendered entries. The
50+
* grid now renders one tile per engine, so that gap is gone and the
51+
* only one left is the honest one: a driver can serve several engines.
52+
*/
53+
question: `Why do Cassandra and ScyllaDB appear separately when you say ${ENGINE_COUNT}?`,
54+
answer: `Because they are two entries in the connection chooser, and the grid shows one tile per entry. The ${ENGINE_COUNT} engines run on ${DRIVER_PLUGIN_COUNT} native driver plugins, because one driver can serve several engines: PostgreSQL also drives Redshift, CockroachDB and PGlite, Cassandra drives ScyllaDB, and libSQL drives Turso.`,
4755
},
4856
{
4957
question: 'Is this abandoned like Sequel Pro?',

resources/js/lib/structured-data.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { GITHUB_REPO_URL } from '@/data/links';
2+
import { ENGINE_COUNT } from '@/data/engines';
23

34
export interface BreadcrumbCrumb {
45
name: string;
@@ -42,7 +43,7 @@ export function buildOrganizationJsonLd(baseUrl: string): object {
4243
height: 256,
4344
},
4445
description:
45-
'TablePro builds a native database client for macOS, iPadOS and iOS covering 25 engines, released as open source under AGPLv3.',
46+
`TablePro builds a native database client for macOS, iPadOS and iOS covering ${ENGINE_COUNT} engines, released as open source under AGPLv3.`,
4647
// All five profiles the footer links, not two of them.
4748
sameAs: [
4849
GITHUB_REPO_URL,

resources/js/pages/Home.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SEOHead from '@/components/seo/seo-head';
1515
import { buildOrganizationJsonLd } from '@/lib/structured-data';
1616
import { GITHUB_REPO_URL } from '@/data/links';
1717
import { STARTER_PRICE, TEAM_SEAT_PRICE } from '@/data/pricing';
18+
import { ENGINE_COUNT } from '@/data/engines';
1819

1920
interface LatestRelease {
2021
version: string | null;
@@ -61,10 +62,10 @@ const HOME_TITLE = 'TablePro - Free, Open Source Database Client for Mac';
6162
* answer engine lifts for "what is TablePro".
6263
*/
6364
const HOME_DESCRIPTION =
64-
'TablePro is a free, open source database client for macOS. 25 engines through native drivers, in a 20 MB Swift app that opens in under a second.';
65+
`TablePro is a free, open source database client for macOS. ${ENGINE_COUNT} engines through native drivers, in a 20 MB Swift app that opens in under a second.`;
6566

6667
const FEATURE_LIST = [
67-
'25 databases through native drivers',
68+
`${ENGINE_COUNT} databases through native drivers`,
6869
'SQL editor with tree-sitter highlighting and Vim mode',
6970
'Editable data grid with deferred commit',
7071
'13 AI providers, bring your own key',
@@ -121,7 +122,7 @@ function buildAppJsonLd(
121122
softwareRequirements: 'macOS 14.0 or later, Apple Silicon or Intel',
122123
description: HOME_DESCRIPTION,
123124
disambiguatingDescription:
124-
'TablePro is a native macOS GUI client for 25 database engines, written in Swift rather than Electron or Java, and released as open source under AGPLv3.',
125+
`TablePro is a native macOS GUI client for ${ENGINE_COUNT} database engines, written in Swift rather than Electron or Java, and released as open source under AGPLv3.`,
125126
// Every URL from canonicalBaseUrl. Four of these used to hardcode the
126127
// production origin while `screenshot` did not, so one object carried
127128
// two origins on any non-production host.

0 commit comments

Comments
 (0)