Skip to content

Commit b474ae3

Browse files
cevhericlaude
andcommitted
feat: add lean internal /platform overview page (open-core narrative)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4761300 commit b474ae3

10 files changed

Lines changed: 181 additions & 54 deletions

File tree

‎src/components/Footer.astro‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const sections = [
99
{ label: "Features", href: "/features" },
1010
{ label: "Providers", href: "/providers" },
1111
{ label: "Database (pre-alpha)", href: "/database" },
12-
{ label: "Platform (beta)", href: "https://platform.libredb.org", external: true },
12+
{ label: "Platform (beta)", href: "/platform" },
1313
{ label: "Tech Stack", href: "/tech-stack" },
1414
{ label: "Deploy", href: "/deploy" },
1515
{ label: "Live Demo", href: "https://app.libredb.org", external: true },

‎src/components/Header.astro‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<a href="/providers" class="text-muted hover:text-fg transition-colors text-sm font-medium">Providers</a>
1818
<a href="/database" class="text-muted hover:text-fg transition-colors text-sm font-medium">Database <span class="text-warn">🧪</span></a>
1919
<a href="/deploy" class="text-muted hover:text-fg transition-colors text-sm font-medium">Deploy</a>
20-
<a href="https://platform.libredb.org" target="_blank" rel="noopener noreferrer" class="text-muted hover:text-fg transition-colors text-sm font-medium">Platform <span aria-hidden="true">↗</span></a>
20+
<a href="/platform" class="text-muted hover:text-fg transition-colors text-sm font-medium">Platform</a>
2121
</div>
2222

2323
<!-- CTA Buttons -->

‎src/components/sections/HomeSection.astro‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ const stats = [
8888
</div>
8989
<p class="mt-2 text-[12.5px] leading-relaxed text-dim">The readable face — the AI-powered SQL IDE for every database. <span class="text-primary">← you are here</span></p>
9090
</a>
91-
<a href="https://platform.libredb.org" target="_blank" rel="noopener noreferrer" class="flex flex-col bg-panel p-5 transition-colors hover:bg-raised">
91+
<a href="/platform" class="flex flex-col bg-panel p-5 transition-colors hover:bg-raised">
9292
<div class="flex items-baseline justify-between gap-2">
93-
<h3 class="text-[15px] font-bold text-ai">platform <span aria-hidden="true">↗</span></h3>
93+
<h3 class="text-[15px] font-bold text-ai">platform</h3>
9494
<span class="text-[10.5px] text-ai">beta</span>
9595
</div>
9696
<p class="mt-2 text-[12.5px] leading-relaxed text-dim">Managed for teams — Database Access Governance: authorized, audited, multi-tenant.</p>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
import SectionHeader from './SectionHeader.astro';
3+
4+
const APP = 'https://platform.libredb.org';
5+
6+
const capabilities = [
7+
{ name: 'Multi-tenant', detail: 'Organizations with isolated data and role-based access — one platform, many teams.' },
8+
{ name: '5-role RBAC', detail: 'Platform Admin, Platform Tech, Tenant Admin, Editor, Viewer — least privilege by default.' },
9+
{ name: 'Connection governance', detail: 'Encrypted credentials with a permission matrix per connection — grant access, never hand out passwords.' },
10+
{ name: 'Query audit log', detail: 'Every query logged with user, timing, and status — full accountability.' },
11+
{ name: 'Plans', detail: 'Free, Team, Business, and Enterprise tiers — runtime-configurable.' },
12+
{ name: 'SSO / OIDC', detail: 'Vendor-agnostic single sign-on (Auth0, Keycloak, Okta, Azure AD).' },
13+
];
14+
15+
const openCore = [
16+
{ tier: 'Studio', line: 'OSS · free · self-hosted · community', cls: 'text-primary' },
17+
{ tier: 'Platform', line: 'closed · paid · cloud · enterprise', cls: 'text-ai' },
18+
];
19+
---
20+
<div class="mx-auto max-w-5xl">
21+
<!-- beta badge -->
22+
<span class="inline-flex items-center gap-2 rounded-full border border-ai/40 px-3 py-1 text-[12.5px] text-ai">
23+
<span aria-hidden="true">●</span> Live · beta · for teams
24+
</span>
25+
26+
<!-- Hero -->
27+
<h1 class="mt-6 text-3xl font-extrabold leading-[1.1] tracking-tight text-bright sm:text-4xl lg:text-5xl">
28+
Database Access<br class="hidden sm:block" />
29+
<span class="text-ai">Governance</span> Platform
30+
</h1>
31+
<p class="mt-5 max-w-2xl text-[15px] leading-relaxed text-muted">
32+
Centralized, authorized, audited database access for teams and enterprises — the managed, team form of LibreDB.
33+
</p>
34+
35+
<!-- Problem callout -->
36+
<div class="mt-8 border-l-2 border-ai/60 bg-panel p-5">
37+
<p class="text-[15px] font-semibold text-bright">Stop distributing connection strings.</p>
38+
<p class="mt-2 text-[13.5px] leading-relaxed text-dim">
39+
Admins control who can access which databases, every query is logged, and teams collaborate securely —
40+
instead of emailing credentials around and hoping for the best.
41+
</p>
42+
</div>
43+
44+
<!-- Capabilities -->
45+
<div class="mt-10">
46+
<SectionHeader title="What teams get" subtitle="Governance, audit, and access control on top of the Studio editor." />
47+
<div class="grid grid-cols-1 gap-px overflow-hidden border border-edge bg-edge sm:grid-cols-2 lg:grid-cols-3">
48+
{capabilities.map((c) => (
49+
<article class="flex flex-col bg-panel p-5">
50+
<h3 class="text-[15px] font-bold text-ai">{c.name}</h3>
51+
<p class="mt-2 text-[13px] leading-relaxed text-dim">{c.detail}</p>
52+
</article>
53+
))}
54+
</div>
55+
</div>
56+
57+
<!-- Open-core model -->
58+
<div class="mt-10">
59+
<SectionHeader title="Open core" subtitle="Studio is free & open; Platform is the managed layer that funds the open-source work." />
60+
<div class="grid grid-cols-1 gap-px overflow-hidden border border-edge bg-edge sm:grid-cols-2">
61+
{openCore.map((o) => (
62+
<article class="flex flex-col bg-panel p-5">
63+
<h3 class={`text-[15px] font-bold ${o.cls}`}>{o.tier}</h3>
64+
<p class="mt-2 text-[13px] leading-relaxed text-dim">{o.line}</p>
65+
</article>
66+
))}
67+
</div>
68+
<p class="mt-4 text-[13.5px] leading-relaxed text-dim">
69+
Both run the same Studio engine (<code class="text-fg">@libredb/studio</code>) — Studio is the single source of truth,
70+
and new editor features flow into Platform with each release. The same proven model behind
71+
Supabase, Metabase, Preset, Neon, and YugabyteDB.
72+
</p>
73+
</div>
74+
75+
<!-- CTA (the only external link) -->
76+
<div class="mt-10 flex flex-col items-start justify-between gap-4 border border-edge bg-panel p-6 sm:flex-row sm:items-center">
77+
<div>
78+
<h3 class="text-[16px] font-bold text-bright">Try the beta</h3>
79+
<p class="mt-1 text-[13.5px] text-dim">Live now at platform.libredb.org — sign in or create a team.</p>
80+
</div>
81+
<a
82+
href={APP}
83+
target="_blank"
84+
rel="noopener noreferrer"
85+
class="inline-flex items-center gap-2 rounded-md bg-primary px-5 py-2.5 text-[14px] font-semibold text-white transition-colors hover:bg-primary-bright"
86+
>
87+
Open the app <span aria-hidden="true">↗</span>
88+
</a>
89+
</div>
90+
</div>

‎src/components/studio/Explorer.astro‎

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,45 @@ const internalCount = sections.length; // total internal sections across schemas
5252
)}
5353
</div>
5454

55-
{schema.external ? (
56-
<ul class="mt-0.5">
55+
<ul class="mt-0.5">
56+
{sections.filter((s) => s.schema === schema.id).map((s) => (
57+
<li data-explorer-item={s.id}>
58+
<div class:list={['exp-row group flex items-center gap-1 pr-2', active === s.id && 'active']}>
59+
<button
60+
type="button"
61+
class="flex h-7 w-5 shrink-0 items-center justify-center text-faint hover:text-fg"
62+
aria-expanded="false"
63+
aria-label={`Toggle ${s.table} columns`}
64+
aria-controls={`${idPrefix}-cols-${s.id}`}
65+
data-explorer-toggle={s.id}
66+
>
67+
<span class="caret-icon" aria-hidden="true">▸</span>
68+
</button>
69+
<a
70+
href={s.slug === '' ? '/' : `/${s.slug}`}
71+
class="flex flex-1 items-center justify-between gap-2 py-1.5 pl-0.5 pr-1"
72+
data-section-link={s.id}
73+
>
74+
<span class="flex items-center gap-2 truncate">
75+
<span class="text-dim" aria-hidden="true">▤</span>
76+
<span class="truncate">{s.table}</span>
77+
</span>
78+
<span class="text-[11px] text-faint">{s.rows}</span>
79+
</a>
80+
</div>
81+
<ul class="hidden pl-9 pb-1" id={`${idPrefix}-cols-${s.id}`} data-explorer-cols={s.id}>
82+
{s.columns.map((c) => (
83+
<li class="flex items-center justify-between py-1 pr-3 text-[12px]">
84+
<span class="flex items-center gap-2 text-muted">
85+
<span class="text-primary/70" aria-hidden="true">◆</span>{c.name}
86+
</span>
87+
<span class="text-[10.5px] uppercase tracking-wide text-faint">{c.type}</span>
88+
</li>
89+
))}
90+
</ul>
91+
</li>
92+
))}
93+
{schema.external && (
5794
<li>
5895
<a
5996
href={schema.external.href}
@@ -68,48 +105,8 @@ const internalCount = sections.length; // total internal sections across schemas
68105
<span class="text-[11px] text-faint" aria-hidden="true">↗</span>
69106
</a>
70107
</li>
71-
</ul>
72-
) : (
73-
<ul class="mt-0.5">
74-
{sections.filter((s) => s.schema === schema.id).map((s) => (
75-
<li data-explorer-item={s.id}>
76-
<div class:list={['exp-row group flex items-center gap-1 pr-2', active === s.id && 'active']}>
77-
<button
78-
type="button"
79-
class="flex h-7 w-5 shrink-0 items-center justify-center text-faint hover:text-fg"
80-
aria-expanded="false"
81-
aria-label={`Toggle ${s.table} columns`}
82-
aria-controls={`${idPrefix}-cols-${s.id}`}
83-
data-explorer-toggle={s.id}
84-
>
85-
<span class="caret-icon" aria-hidden="true">▸</span>
86-
</button>
87-
<a
88-
href={s.slug === '' ? '/' : `/${s.slug}`}
89-
class="flex flex-1 items-center justify-between gap-2 py-1.5 pl-0.5 pr-1"
90-
data-section-link={s.id}
91-
>
92-
<span class="flex items-center gap-2 truncate">
93-
<span class="text-dim" aria-hidden="true">▤</span>
94-
<span class="truncate">{s.table}</span>
95-
</span>
96-
<span class="text-[11px] text-faint">{s.rows}</span>
97-
</a>
98-
</div>
99-
<ul class="hidden pl-9 pb-1" id={`${idPrefix}-cols-${s.id}`} data-explorer-cols={s.id}>
100-
{s.columns.map((c) => (
101-
<li class="flex items-center justify-between py-1 pr-3 text-[12px]">
102-
<span class="flex items-center gap-2 text-muted">
103-
<span class="text-primary/70" aria-hidden="true">◆</span>{c.name}
104-
</span>
105-
<span class="text-[10.5px] uppercase tracking-wide text-faint">{c.type}</span>
106-
</li>
107-
))}
108-
</ul>
109-
</li>
110-
))}
111-
</ul>
112-
)}
108+
)}
109+
</ul>
113110
</div>
114111
))}
115112
</nav>

‎src/data/schemas.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// src/data/schemas.ts
22
// Schema grouping for the Explorer tree. One connection (libredb.org), three
33
// schemas: studio (production hero), database (pre-alpha engine), platform
4-
// (commercial, external). Sections belong to studio or database; platform has
5-
// no internal sections — only an external link.
4+
// (commercial, now has an internal overview page at /platform plus an external
5+
// live-app link to platform.libredb.org).
66

77
export interface SchemaMeta {
88
id: 'studio' | 'database' | 'platform';
@@ -25,6 +25,6 @@ export const schemas: SchemaMeta[] = [
2525
label: 'platform',
2626
badge: 'beta · teams',
2727
badgeClass: 'text-ai border border-ai/40',
28-
external: { label: 'overview', href: 'https://platform.libredb.org' },
28+
external: { label: 'open app', href: 'https://platform.libredb.org' },
2929
},
3030
];

‎src/data/section-seo.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ export const sectionSeo: Record<string, object[]> = {
4141
softwareVersion: 'pre-alpha (0.0.x)',
4242
},
4343
],
44+
platform: [
45+
{
46+
'@context': 'https://schema.org',
47+
'@type': 'WebApplication',
48+
name: 'LibreDB Platform',
49+
applicationCategory: 'BusinessApplication',
50+
description: 'Managed, multi-tenant Database Access Governance for teams — authorized, audited database access built on the open-source LibreDB Studio engine.',
51+
operatingSystem: 'Web',
52+
url: 'https://platform.libredb.org',
53+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
54+
softwareVersion: 'beta',
55+
},
56+
],
4457
docker_compose: [
4558
{
4659
'@context': 'https://schema.org',

‎src/data/sections.test.ts‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ test('every section has page SEO fields', () => {
2626
}
2727
});
2828

29-
test('every section declares a schema of studio or database', () => {
29+
test('every section declares a schema of studio, database, or platform', () => {
3030
for (const s of sections) {
31-
expect(['studio', 'database']).toContain(s.schema);
31+
expect(['studio', 'database', 'platform']).toContain(s.schema);
3232
}
3333
});
3434

@@ -63,3 +63,11 @@ test('three database-schema sections exist with correct slugs', () => {
6363
expect(sectionById['database_architecture'].slug).toBe('database-architecture');
6464
expect(sectionById['database_reliability'].slug).toBe('database-reliability');
6565
});
66+
67+
test('platform overview section exists (internal /platform)', () => {
68+
const p = sectionById['platform'];
69+
expect(p).toBeDefined();
70+
expect(p.slug).toBe('platform');
71+
expect(p.table).toBe('overview');
72+
expect(p.schema).toBe('platform');
73+
});

‎src/data/sections.ts‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface SectionMeta {
2323
slug: string; // URL slug ('' = home at '/')
2424
pageTitle: string; // <title> when this section is its own page
2525
pageDescription: string;
26-
schema: 'studio' | 'database'; // Explorer grouping; platform has no sections
26+
schema: 'studio' | 'database' | 'platform'; // Explorer grouping
2727
}
2828

2929
export const sections: SectionMeta[] = [
@@ -238,6 +238,23 @@ export const sections: SectionMeta[] = [
238238
pageDescription: 'Copy-paste docker-compose.example.yml for LibreDB Studio. Run the open-source SQL IDE with one command using the ghcr.io/libredb/libredb-studio image. Includes every environment variable, SQLite/PostgreSQL storage, and OIDC SSO options.',
239239
schema: 'studio',
240240
},
241+
{
242+
id: 'platform',
243+
table: 'overview',
244+
query: 'SELECT capability, detail FROM access_governance;',
245+
rows: 6,
246+
cols: 2,
247+
execMs: 5,
248+
columns: [
249+
{ name: 'capability', type: 'TEXT' },
250+
{ name: 'detail', type: 'TEXT' },
251+
],
252+
explain: 'LibreDB Platform: managed, multi-tenant Database Access Governance for teams — authorized, audited database access built on the open-source LibreDB Studio engine. Live in beta; the commercial layer of the open-core model.',
253+
slug: 'platform',
254+
pageTitle: 'LibreDB Platform — Database Access Governance for Teams (beta)',
255+
pageDescription: 'Stop distributing connection strings. LibreDB Platform is managed, multi-tenant database access governance — role-based access, encrypted credentials, per-connection permissions, and full query audit logging — built on the open-source LibreDB Studio engine. Live in beta.',
256+
schema: 'platform',
257+
},
241258
];
242259

243260
export const sectionById: Record<string, SectionMeta> = Object.fromEntries(

‎src/pages/[section].astro‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import DockerComposeSection from '../components/sections/DockerComposeSection.as
1717
import DatabaseSection from '../components/sections/DatabaseSection.astro';
1818
import DatabaseArchitectureSection from '../components/sections/DatabaseArchitectureSection.astro';
1919
import DatabaseReliabilitySection from '../components/sections/DatabaseReliabilitySection.astro';
20+
import PlatformSection from '../components/sections/PlatformSection.astro';
2021
2122
export function getStaticPaths() {
2223
return sections
@@ -36,6 +37,7 @@ const COMPONENTS: Record<string, any> = {
3637
database: DatabaseSection,
3738
database_architecture: DatabaseArchitectureSection,
3839
database_reliability: DatabaseReliabilitySection,
40+
platform: PlatformSection,
3941
};
4042
4143
const { id } = Astro.props as { id: string };

0 commit comments

Comments
 (0)