diff --git a/.gitignore b/.gitignore index 1297b20..8862946 100644 --- a/.gitignore +++ b/.gitignore @@ -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.* diff --git a/docs/STUDIO.md b/docs/STUDIO.md index 9df62e5..8c9685f 100644 --- a/docs/STUDIO.md +++ b/docs/STUDIO.md @@ -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: diff --git a/docs/assets/thimbledb-studio.png b/docs/assets/thimbledb-studio.png new file mode 100644 index 0000000..77f8af5 Binary files /dev/null and b/docs/assets/thimbledb-studio.png differ diff --git a/site/scripts/prepare-assets.mjs b/site/scripts/prepare-assets.mjs index eb7a0d4..55c2b0c 100644 --- a/site/scripts/prepare-assets.mjs +++ b/site/scripts/prepare-assets.mjs @@ -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 }); @@ -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(` diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index 89b2eb2..b110da0 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -133,6 +133,50 @@ const websiteSchema = { +
+
+
+
+

ThimbleDB Studio

+

Inspect scoped data without bypassing the authority.

+
+

+ 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. +

+
+ +
+ + + + ThimbleDB Studio showing a read-only tenant scope, a bounded indexed query, and product documents + + +
+ Read-only by default + Explicit scope grants + Bounded query plans + + Explore Studio + +
+
+
+ +
diff --git a/site/src/styles/global.css b/site/src/styles/global.css index ebfcd60..acfa305 100644 --- a/site/src/styles/global.css +++ b/site/src/styles/global.css @@ -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)); @@ -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; @@ -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); diff --git a/site/tests/site.spec.ts b/site/tests/site.spec.ts index 2da94d5..03eff5a 100644 --- a/site/tests/site.spec.ts +++ b/site/tests/site.spec.ts @@ -25,7 +25,7 @@ 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"), @@ -33,11 +33,17 @@ test("homepage presents the product and complete SEO metadata", async ({ 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/"); @@ -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 ({