Skip to content
Merged
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
135 changes: 135 additions & 0 deletions docs/designs/2026-07-15-declutter-shell-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Declutter the studio shell — design

**Date:** 2026-07-15 · **Branch:** `design/declutter-shell`

## Problem

The IDE-shell concept is loved, but the page is overloaded. Measured on the
desktop homepage (multi-agent audit, 6 dimensions + adversarial critique):

- ~70 interactive chrome elements render before any marketing content
(~66 tab stops before the first CTA for keyboard users).
- 19 buttons are `data-action="notice"` no-ops occupying tab order.
- The same datum repeats: row count ×3, exec time ×2, `home.sql` ×3,
Connected/Online ×4, version ×3 (`v0.9.29` twice + a conflicting `v1.2.5`),
GitHub reachable from 4 chrome slots, open-source/MIT stated 4×.
- 5–6 accent hues compete in one viewport (rainbow DB names, 4-color stats,
multi-color badges); no single accent moment.
- QueryChrome stacks 5 horizontal strips above the H1 (~y=395px).

## Principles

1. **One strip, one job.** No datum appears twice in the shell.
2. **Semantic color budget.** Blue = interactive/CTA (filled blue on exactly
two elements: Live Demo + RUN). Green = connection state, one animated
pulse total (StatusBar). Amber = warning/early-beta only. Purple = AI only.
Red = errors only. Everything else zinc.
3. **Progressive disclosure, not deletion.** The console-notice gags are demo
funnels — they survive one click deeper (Query menu, `⋯` overflow, ⌘K
palette) or become non-focusable spans. The IDE fiction stays.
4. **Chrome type scale:** 11 / 12 / 13 / 15 px only (no half-pixel steps).
5. Mono stays everywhere (spec: mono-first). Body copy gets reading
ergonomics (measure/leading), not a sans face.

## Changes

### TopBar (12 items → 5)
Cut: `▤ +` dead glyphs, the whole `libredb.org` connection chip (owner:
"zaten bu sitedeyim" — the visitor is already here), `PRODUCTION • ONLINE`,
separate Online pill, Monitoring button, `v0.9.29`. Monitoring gag re-homed
as a ⌘K palette entry. Keep: brand, ★ GitHub, Playground, ⌘K, Live Demo
(filled). Mobile top bar mirrors this: brand instead of URL, no ONLINE line.

### QueryChrome (5 strips → 3 bands, 29 interactive → 7 visible)
Owner-directed final form (tightened beyond the audit consensus):
- Tab bar: `{table}.sql` + ✕ (kept gag), `+` demoted to span. README.md tab
removed — GitHub lives in TopBar + StatusBar.
- Both toolbars deleted. **RUN** moved into the editor gutter, in front of
the SQL statement (DataGrip-style); ⌘+Enter chip dropped (shortcut still
wired, surfaced via `title`). Query/Save/Copy/BEGIN/SANDBOX/EDIT/IMPORT/
Format/Clear/Lines/AI removed from chrome (Copy/Export/AI stay reachable
via ⌘K palette).
- Results row: Results, ✦ Explain (single trigger), and a `⋯` overflow with
History/Saved/Charts/NL2SQL/Autopilot/Pivot/Diff/Dashboard (notices
intact). Docs tab and the `{rows} · {cols} · {ms}` + Export meta removed —
RUN's console toast still reports rows/ms in-fiction.
- Result-meta strip deleted — it was 100 % duplicate data.
- Net: keyboard path to the hero CTA is 28 tab stops (was ~66).

### StatusBar (15 items → 11)
Keep: Connected (the one animated pulse), the real links (promoted to
`text-muted`), one faint identity cluster `Ln 1, Col 1 · UTF-8 · SQL · ◆ v0.9.29`.
Cut: `LibreDB:latest`, `public`, `{table}.sql`, `{rows} rows`, duplicate brand.
Added later at owner request: X, YouTube, Instagram and Reddit (r/libredb)
links — the status bar is the site's only footer, the conventional home for
socials, and all four channels are actively maintained.

### Explorer
Connection box, CONNECTIONS label, the "▤ Explorer · 14" header row and the
search placeholder all removed (owner: the visitor is already on
libredb.org; the tree speaks for itself — sidebar is now search + tree). Schema badges quieted to lowercase `text-dim`, no border/accent.
Row counts stay (core "table = page" loop) but carets dim at rest and
brighten on hover/active. Rows default `text-muted`, active bright
(Railway dim pattern). Column `◆` icons → `text-dim`. Sidebar footer
deleted (duplicate Connected + conflicting `v1.2.5`).
Toggles: `tabindex="-1"`, ≥24 px hit area (36 px in drawer).

### HomeSection
One-fact-one-owner rule (owner-directed final form): badge owns the
license/open-source claim, H1 owns AI + "SQL IDE", the subtitle owns the
engine list + SSO, the CTAs own demo/deploy. Everything that restated one
of those went:
- **Stats grid deleted** — all four tiles (1-click deploy, MIT, AI, SSO)
duplicated facts already visible in the same viewport.
- Subtitle rewritten: "One editor for 'PostgreSQL' … 'Redis' — in your
browser, with SSO built in." The 7 DB names render as **quoted green SQL
string literals** — one hue, in-fiction, replacing the 5-hue rainbow.
- Badge pill kept (the single open-source/MIT signal), dot static.
- Tip comments and the open-core footnote deleted entirely — the explorer
rows read as navigation on their own, and the family cards' status labels
already tell the open-core story (the sustainability pitch belongs on
/platform or the FAQ); family heading → "one core, three products";
studio card no longer a self-link (div, keeps ring + "you are here");
sponsor line dropped (StatusBar link covers it).
- Export payload now ships the engine list instead of the deleted stats.
- **Promo video** (owner request): the 60s YouTube promo sits under the
CTAs as a click-to-load facade — self-hosted poster + play button, zero
third-party bytes/cookies at rest; on play, studio.ts swaps in a
`youtube-nocookie.com` iframe with autoplay. Keeps the page light and
consent-clean while giving the hero its "real product in motion" proof.

### Accessibility bundle
Skip-to-content link (in-character), `aria-label="Explorer"` on asides,
global `:focus-visible` ring in primary, mobile drawer ✕ and Explain ≥40 px
hit areas, mobile CTA relabeled `▶ Live Demo` (was a dishonest `RUN`),
toast NOTICES lose leading glyphs (SR noise) and pause on keyboard focus,
~15 fake buttons demoted to spans (delegated handler works on any Element).

### Deploy page
Synced with `distribution/channels.yaml` in the studio repo (the channel
inventory): Homebrew, Snap Store and GitHub Releases (tarballs + .deb/.rpm)
were live channels missing from the site — now first-class registry cards.
Every "planned" target (26 of the original 39) is **deleted from the data**
(owner call: the site lists only what ships today; roadmap lives in
channels.yaml). The all-planned "Cloud hyperscalers" category is replaced
by one positive comment line — "any Docker host works today" — which says
more than five aspirational cards did. Summary strip and the investor
star-total band are deleted (counts duplicated the sections below; summing
third-party platforms' stars overstated traction). Also fixed a real bug:
the star-refresh script used `import` inside a `data-astro-rerun` inline
script, which throws at runtime — live star counts had silently never
refreshed; the helper is now mirrored inline.

### Section-page color discipline (cheap, systemic)
Features kickers: 7-color map → `text-dim` (AI keeps `text-ai`; SECURITY no
longer error-red). TechStack layer headers + Reliability stat numbers →
`text-bright`. Providers keeps its syntax-mimicry colors, but Redis moves off
the `text-bad` error token.

## Out of scope (follow-ups)
- Docker-compose page: `<details>` per env-var group; nest under deploy in tree.
- Deploy page: generate `deploy-targets.ts` from `distribution/channels.yaml`
(single source of truth, kills drift).
- Hero "one live moment" (query types itself once, VT-safe).
- Trust row under stats (GitHub stars / quote as SQL comment).
- Tokenized type scale in `@theme` + CI guard against new `text-[..px]` in chrome.
Binary file added public/video/promo-poster.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/sections/DatabaseCtaFooter.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const DOCS = 'https://github.com/libredb/libredb-database/tree/main/docs/guides'

<div class="mt-10 border border-warn/40 bg-panel p-6">
<h3 class="flex items-center gap-2 text-[15px] font-bold text-warn">
<span aria-hidden="true">🧪</span> Pre-alpha — not production ready
<span aria-hidden="true">🧪</span> early-beta — not production ready
</h3>
<p class="mt-2 max-w-2xl text-[13.5px] leading-relaxed text-dim">
LibreDB is early (<code class="text-fg">0.0.x</code>). The architecture is in place and every line of the core is
Expand Down
10 changes: 5 additions & 5 deletions src/components/sections/DatabaseReliabilitySection.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import SectionHeader from './SectionHeader.astro';
import DatabaseCtaFooter from './DatabaseCtaFooter.astro';

const numbers = [
{ n: '~712', l: 'lines of shipped code', cls: 'text-primary' },
{ n: '0', l: 'runtime dependencies', cls: 'text-ok' },
{ n: '2.83 kB', l: 'bundled (min+brotli)', cls: 'text-ai' },
{ n: '100%', l: 'core line coverage', cls: 'text-warn' },
{ n: '~712', l: 'lines of shipped code', cls: 'text-bright' },
{ n: '0', l: 'runtime dependencies', cls: 'text-bright' },
{ n: '2.83 kB', l: 'bundled (min+brotli)', cls: 'text-bright' },
{ n: '100%', l: 'core line coverage', cls: 'text-bright' },
];

const notForYet = [
{ area: 'Production at scale', why: "It is pre-alpha; today's beachhead is test/dev." },
{ area: 'Production at scale', why: "It is early-beta; today's beachhead is test/dev." },
{
area: 'Secondary indexes / planner',
why: 'Queries are O(n) scans by design in v1 (on the roadmap).',
Expand Down
4 changes: 2 additions & 2 deletions src/components/sections/DatabaseSection.astro
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ const principles = [
---

<div class="mx-auto max-w-5xl">
<!-- Pre-alpha badge -->
<!-- early-beta badge -->
<span class="inline-flex items-center gap-2 rounded-full border border-warn/40 px-3 py-1 text-[12.5px] text-warn">
<span aria-hidden="true">🧪</span> Pre-alpha · open source (MIT)
<span aria-hidden="true">🧪</span> early-beta · open source (MIT)
</span>

<!-- Hero -->
Expand Down
107 changes: 38 additions & 69 deletions src/components/sections/DeploySection.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SectionHeader from './SectionHeader.astro';
import PlatformCard from '../deploy/PlatformCard.astro';
import { deployTargets, starRepos } from '../../data/deploy-targets';
import { deployCategories } from '../../data/deploy-categories';
import { getStars, formatStars } from '../../lib/github-stars';
import { getStars } from '../../lib/github-stars';

const stars = await getStars(starRepos);

Expand All @@ -13,40 +13,17 @@ const targetsByCategory = (id: string) =>
deployTargets.filter((t) => t.category === id).sort((a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status]);

const official = deployTargets.filter((t) => t.status === 'official');
const totalStars = Object.values(stars).reduce((sum, n) => sum + n, 0);

const platformCount = deployTargets.length;
const installMethodCount = deployTargets.filter((t) => t.category === 'registry').length;
const plannedNames = deployTargets.filter((t) => t.status === 'planned').map((t) => t.name);

const catColors = ['text-primary', 'text-ok', 'text-ai', 'text-warn', 'text-bad'];
const summary = sortedCategories.map((c, i) => ({
title: c.title,
count: deployTargets.filter((t) => t.category === c.id).length,
color: catColors[i % catColors.length],
}));
---

<div class="mx-auto max-w-6xl">
<SectionHeader
title="Deploy anywhere"
subtitle={`One open-source image, every layer of the deploy stack — ${platformCount}+ platforms and ${installMethodCount} install methods.`}
subtitle={`One open-source image — ${installMethodCount} install methods, ${official.length} official one-click marketplaces, and any host that runs a container.`}
/>

<!-- Category summary strip -->
<div class="grid grid-cols-2 gap-px overflow-hidden border border-edge bg-edge md:grid-cols-3 lg:grid-cols-5">
{
summary.map((c) => (
<div class="bg-panel p-5">
<div class={`text-3xl font-bold ${c.color}`}>{c.count}</div>
<div class="mt-2 text-[12.5px] leading-snug text-dim">{c.title}</div>
</div>
))
}
</div>

<!-- Official one-click integrations -->
<section class="mt-10">
<section class="mt-2">
<h3 class="text-[15px] font-semibold text-bright">Official one-click integrations</h3>
<p class="mt-1 text-[13px] text-dim">Listed in their marketplaces — deploy in a click, today.</p>
<div class="mt-4 grid gap-4 md:grid-cols-2">
Expand Down Expand Up @@ -90,7 +67,7 @@ const summary = sortedCategories.map((c, i) => ({
</div>
</section>

<!-- Category grids -->
<!-- Category grids — every card is a target that works today -->
{
sortedCategories.map((cat) => (
<section class="mt-10">
Expand All @@ -105,17 +82,13 @@ const summary = sortedCategories.map((c, i) => ({
))
}

<!-- Investor depth band -->
<section class="mt-10 border border-edge-strong bg-panel p-6 text-center md:p-8">
<p class="mb-2 text-sm text-muted">Embedded across open-source ecosystems totaling</p>
<div class="mb-3 text-3xl font-bold text-primary md:text-4xl">
<span data-stars-total>{formatStars(totalStars)}</span>+ GitHub stars
</div>
<p class="mx-auto max-w-2xl text-sm text-muted">
LibreDB Studio doesn't just support these platforms — it ships inside communities that millions of developers
already trust. More on the roadmap: {plannedNames.slice(0, 8).join(', ')}, and more.
</p>
</section>
<!-- Everything else is covered by the image itself -->
<p class="mt-8 text-[13px] leading-relaxed">
<span class="sql-com"
>-- anywhere else: it's one container image. ECS / Fargate, Cloud Run, ACI, App Platform — any Docker host works
today.</span
>
</p>

<!-- CTA -->
<section class="mt-8 flex flex-wrap gap-3">
Expand All @@ -141,35 +114,31 @@ const summary = sortedCategories.map((c, i) => ({
</section>
</div>

<script data-astro-rerun>
import { formatStars } from '../../lib/github-stars';

async function refreshStars() {
const spans = Array.from(document.querySelectorAll<HTMLElement>('[data-stars-repo]'));
await Promise.all(
spans.map(async (span) => {
const repo = span.getAttribute('data-stars-repo');
if (!repo) return;
try {
const res = await fetch(`https://api.github.com/repos/${repo}`, {
headers: { Accept: 'application/vnd.github+json' },
});
if (!res.ok) return;
const data = await res.json();
if (typeof data.stargazers_count !== 'number') return;
span.setAttribute('data-stars-count', String(data.stargazers_count));
span.textContent = formatStars(data.stargazers_count);
} catch {
/* keep the baked-in number */
}
}),
);
const total = Array.from(document.querySelectorAll<HTMLElement>('[data-stars-count]')).reduce(
(sum, el) => sum + (Number(el.getAttribute('data-stars-count')) || 0),
0,
);
const totalEl = document.querySelector<HTMLElement>('[data-stars-total]');
if (totalEl && total > 0) totalEl.textContent = formatStars(total);
}
refreshStars();
<script is:inline data-astro-rerun>
// Live-refresh the baked-in star counts. Runs as a classic script (an import
// here would throw "Cannot use import statement outside a module"), so
// formatStars from lib/github-stars.ts is mirrored inline — keep in sync.
(() => {
const fmt = (n) => {
if (n < 1000) return String(n);
const k = n / 1000;
const rounded = k >= 100 ? Math.round(k) : Math.round(k * 10) / 10;
return `${rounded}k`;
};
document.querySelectorAll('[data-stars-repo]').forEach(async (span) => {
const repo = span.getAttribute('data-stars-repo');
if (!repo) return;
try {
const res = await fetch(`https://api.github.com/repos/${repo}`, {
headers: { Accept: 'application/vnd.github+json' },
});
if (!res.ok) return;
const data = await res.json();
if (typeof data.stargazers_count !== 'number') return;
span.textContent = fmt(data.stargazers_count);
} catch {
/* keep the baked-in number */
}
});
})();
</script>
13 changes: 7 additions & 6 deletions src/components/sections/FeaturesSection.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import SectionHeader from './SectionHeader.astro';

type Cat = 'EDITOR' | 'AI' | 'CORE' | 'DATA' | 'SCHEMA' | 'SECURITY' | 'TOOLING';

// Color budget: purple marks the AI differentiator; every other category stays quiet.
const catColor: Record<Cat, string> = {
AI: 'text-ai',
EDITOR: 'text-primary',
CORE: 'text-ok',
DATA: 'text-warn',
SCHEMA: 'text-primary',
SECURITY: 'text-bad',
TOOLING: 'text-muted',
EDITOR: 'text-dim',
CORE: 'text-dim',
DATA: 'text-dim',
SCHEMA: 'text-dim',
SECURITY: 'text-dim',
TOOLING: 'text-dim',
};

interface Feature {
Expand Down
Loading
Loading