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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ site/public/assets/logo-320.webp
site/public/assets/logo-384.webp
site/public/assets/logo-640.webp
site/public/assets/logo-96.webp
site/public/assets/studio-960.webp
site/public/assets/studio-1600.webp
*.log
.env
.env.*
Expand Down
5 changes: 5 additions & 0 deletions docs/STUDIO.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ Studio never receives object-storage credentials or an authority write key.
The `thimble.admin` role enables administrative operations but does not grant
access to every data scope.

![ThimbleDB Studio showing a read-only tenant scope, a bounded indexed query, and product documents](https://thimbledb.com/assets/studio-1600.webp)

The screenshot uses demo data. Studio remains read-only until the selected
scope ID is explicitly confirmed for that browser session.

## Capabilities

Studio provides:
Expand Down
Binary file added docs/assets/thimbledb-studio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions site/scripts/prepare-assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ const sourceLogo = path.join(
"public",
"thimbledb-logo.png",
);
const sourceStudio = path.join(
repositoryRoot,
"docs",
"assets",
"thimbledb-studio.png",
);
const assetsDirectory = path.join(siteRoot, "public", "assets");

await mkdir(assetsDirectory, { recursive: true });
Expand Down Expand Up @@ -45,6 +51,16 @@ await sharp(sourceLogo)
.webp({ quality: 84 })
.toFile(path.join(assetsDirectory, "logo-96.webp"));

await sharp(sourceStudio)
.resize({ width: 960, withoutEnlargement: true })
.webp({ quality: 86 })
.toFile(path.join(assetsDirectory, "studio-960.webp"));

await sharp(sourceStudio)
.resize({ width: 1600, withoutEnlargement: true })
.webp({ quality: 88 })
.toFile(path.join(assetsDirectory, "studio-1600.webp"));

const socialBackground = Buffer.from(`
<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
<defs>
Expand Down
44 changes: 44 additions & 0 deletions site/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,50 @@ const websiteSchema = {
</div>
</section>

<section class="section-block studio-section">
<div class="section-shell">
<div class="section-heading">
<div>
<p class="eyebrow">ThimbleDB Studio</p>
<h2>Inspect scoped data without bypassing the authority.</h2>
</div>
<p>
Browse declared collections, run bounded queries, inspect plans and
indexes, and use guarded maintenance tools through the same OIDC
session and explicit scope grants as the application.
</p>
</div>

<figure class="studio-preview">
<a href="/docs/studio/" aria-label="Open the ThimbleDB Studio guide">
<picture>
<source
srcset="/assets/studio-960.webp 960w, /assets/studio-1600.webp 1600w"
sizes="(max-width: 720px) calc(100vw - 2rem), min(1200px, calc(100vw - 5rem))"
type="image/webp"
/>
<img
src="/assets/studio-1600.webp"
width="1600"
height="1000"
loading="lazy"
decoding="async"
alt="ThimbleDB Studio showing a read-only tenant scope, a bounded indexed query, and product documents"
/>
</picture>
</a>
<figcaption>
<span>Read-only by default</span>
<span>Explicit scope grants</span>
<span>Bounded query plans</span>
<a class="text-link" href="/docs/studio/">
Explore Studio <span aria-hidden="true">→</span>
</a>
</figcaption>
</figure>
</div>
</section>

<section class="section-block code-section">
<div class="section-shell">
<div class="section-heading">
Expand Down
73 changes: 73 additions & 0 deletions site/src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,60 @@ summary {
font-size: 1.02rem;
}

.studio-section {
background:
radial-gradient(circle at 88% 10%, rgb(12 114 255 / 0.12), transparent 28rem),
linear-gradient(180deg, #f7faff, #edf4ff) !important;
}

.studio-preview {
overflow: hidden;
margin: 0;
border: 1px solid #b9cbe3;
border-radius: 1.35rem;
background: #071a35;
box-shadow: 0 30px 80px rgb(5 30 65 / 0.2);
}

.studio-preview > a {
display: block;
padding: clamp(0.45rem, 1.2vw, 0.9rem);
}

.studio-preview picture,
.studio-preview img {
display: block;
width: 100%;
}

.studio-preview img {
height: auto;
border-radius: 0.8rem;
}

.studio-preview figcaption {
display: flex;
align-items: center;
gap: 0.65rem 1rem;
padding: 0.95rem 1.2rem 1.1rem;
color: #c9dcf5;
background: #06162f;
font-size: 0.82rem;
font-weight: 700;
}

.studio-preview figcaption > span {
border: 1px solid rgb(120 178 245 / 0.22);
border-radius: 999px;
padding: 0.35rem 0.6rem;
background: rgb(255 255 255 / 0.05);
}

.studio-preview figcaption .text-link {
margin-left: auto;
color: #78ddff;
}

.fit-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
Expand Down Expand Up @@ -1657,6 +1711,16 @@ summary {
margin: 1rem 0;
}

.prose img {
display: block;
max-width: 100%;
height: auto;
margin: 1.5rem 0;
border: 1px solid var(--slate-200);
border-radius: 0.9rem;
box-shadow: var(--shadow-md);
}

.prose h1 + p {
color: var(--slate-600);
font-size: 1.1rem;
Expand Down Expand Up @@ -2244,6 +2308,15 @@ summary {
gap: 1.4rem;
}

.studio-preview figcaption {
align-items: flex-start;
flex-direction: column;
}

.studio-preview figcaption .text-link {
margin-left: 0;
}

.section-heading h2,
.final-cta h2 {
font-size: clamp(2.3rem, 12vw, 3.5rem);
Expand Down
22 changes: 21 additions & 1 deletion site/tests/site.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,25 @@ test("homepage presents the product and complete SEO metadata", async ({
page.locator('script[type="application/ld+json"]'),
).toHaveCount(2);
await expect(
page.getByRole("img", { name: "ThimbleDB" }),
page.getByRole("img", { name: "ThimbleDB", exact: true }),
).toHaveJSProperty("complete", true);
await expect(
page.getByText("npx thimbledb@latest create my-app"),
).toBeVisible();
await expect(
page.getByText("Bounded indexed query", { exact: true }),
).toBeVisible();
const studioImage = page.getByRole("img", {
name: /Studio showing a read-only tenant scope/i,
});
await studioImage.scrollIntoViewIfNeeded();
await expect(studioImage).toHaveJSProperty("complete", true);
await assertNoHorizontalOverflow(page);
});

test("repository documentation renders with rewritten internal links", async ({
page,
request,
}) => {
await page.goto("/docs/quickstart/");

Expand All @@ -52,6 +58,20 @@ test("repository documentation renders with rewritten internal links", async ({
).toHaveAttribute("href", "/docs/authentication/");
await expect(page.locator('.prose a[href$=".md"]')).toHaveCount(0);
await assertNoHorizontalOverflow(page);

await page.goto("/docs/studio/");
await expect(
page.getByRole("img", {
name: /Studio showing a read-only tenant scope/i,
}),
).toHaveAttribute(
"src",
"https://thimbledb.com/assets/studio-1600.webp",
);
expect((await request.get("/assets/studio-1600.webp")).ok()).toBe(
true,
);
await assertNoHorizontalOverflow(page);
});

test("documentation search returns relevant repository pages", async ({
Expand Down
Loading