Docs overview update

This commit is contained in:
Konstantin Wohlwend 2026-04-13 15:30:11 -07:00
parent 5573927429
commit 389199b57e
5 changed files with 420 additions and 132 deletions

View File

@ -109,6 +109,7 @@ To see all development ports, refer to the index.html of `apps/dev-launchpad/pub
- Unless very clearly equivalent from types, prefer explicit null/undefinedness checks over boolean checks, eg. `foo == null` instead of `!foo`.
- Ensure **aggressively** that all code has low coupling and high cohesion. This is really important as it makes sure our code remains consistent and maintainable. Eagerly refactor things into better abstractions and look out for them actively.
- Whenever you change the URL of a page in the docs (or remove one), add a redirect in the docs-mintlify/docs.json file to make sure we don't lose any SEO juice.
- When you made frontend (or docs, dashboard, demo, etc.) changes, and you have a browser MCP in your list of MCP tools, make sure to test the changes in the browser MCP.
### Code-related
- Use ES6 maps instead of records wherever you can.

View File

@ -187,3 +187,57 @@ A: In `apps/backend/src/app/api/latest/auth/oauth/callback/[provider_id]/route.t
Q: How should OAuth E2E tests assert callback failures after handler-based error redirects?
A: In OAuth callback/merge strategy E2E tests, assert `307` plus parsed `location` query params (`error`, `errorCode`, `error_description`, `message`, and optionally `details`) instead of snapshotting old `4xx` JSON error responses. This matches current callback semantics and avoids brittle encoded-URL snapshots.
Q: How should auth sign-up-rules OAuth rejection tests assert failures now?
A: In `apps/e2e/tests/backend/endpoints/api/v1/auth/sign-up-rules.test.ts`, OAuth rejection cases should assert the callback redirect (`307`) and validate `location` query params (`error=server_error`, `errorCode=SIGN_UP_REJECTED`, `message`/`error_description`, and JSON `details`) rather than expecting direct `403` response bodies.
Q: Where is the docs-mintlify homepage hero/island content defined?
A: The top homepage island on docs-mintlify is authored directly in `docs-mintlify/index.mdx` as the first `not-prose` block, so copy/design/CTA updates should be made there.
Q: Why can a docs-mintlify snippet fail validation when importing React?
A: `mint validate` rejects non-local imports in `/snippets/*.jsx` (for example `import { useState } from "react"`), so snippets must avoid package imports and rely on zero-import component code.
Q: Where was the docs homepage Quick Start block defined?
A: The Quick Start section on the docs-mintlify homepage lived directly in `docs-mintlify/index.mdx` right after `<HomePromptIsland />`, so removing that full `<div className="mx-auto mt-16 ...">` block removes the entire Quick Start UI.
Q: How is the docs homepage "Explore Apps" step now rendered?
A: It is embedded inside the "Navigate Through Our Docs" timeline as a single step via `DocsAppsHomeGrid` from `docs-mintlify/snippets/docs-apps-home-grid.jsx`, using app icon SVGs in a dashboard-style quick-access grid.
Q: Why did docs-mintlify throw `ReferenceError: agentSetupPromptPlaceholder is not defined` on the homepage?
A: In snippet components (`/snippets/*.jsx`), top-level constants can fail to resolve in the runtime-compiled output; moving constants like `agentSetupPromptPlaceholder` and `appLinks` inside the exported component function avoids the reference error.
Q: How was the docs homepage prompt island restyled for stronger contrast?
A: `docs-mintlify/snippets/home-prompt-island.jsx` now uses an inverted minimal palette (`bg-[#0b0b0d]` in light mode and `dark:bg-zinc-50` in dark mode) with simplified borders, reduced visual effects, and custom button styles for cleaner contrast.
Q: Why did `DocsAppsHomeGrid` throw `ReferenceError` for helper functions despite passing lint?
A: In docs-mintlify snippets, top-level helper function references can disappear in the runtime-compiled output even when `mint validate` passes; keep helper functions/constants inside the exported component body to avoid runtime `ReferenceError`s.
Q: How to ensure the manual-installation CTA remains visible on the inverted dark-mode hero?
A: In `docs-mintlify/snippets/home-prompt-island.jsx`, force explicit dark-mode button contrast with strong dark variant classes (for example `dark:bg-zinc-100` and `dark:!text-zinc-900`) so Mintlify base link styles cannot wash out label text.
Q: How can the docs homepage prompt feel compact while still implying multi-line content?
A: In `docs-mintlify/snippets/home-prompt-island.jsx`, use a low-height read-only textarea (`h-28`) with `overflow-hidden`, place the copy button as an absolute suffix inside the field, and add a bottom gradient overlay to hint hidden lines.
Q: Where is the docs homepage recommended-order timeline controlled?
A: The ordered step blocks are authored directly in `docs-mintlify/index.mdx` inside the "Navigate Through Our Docs" section, so adding steps like `SDK Reference` and `REST API` is done by inserting new timeline `<div className="relative ...">` blocks there.
Q: Why can the docs copy button throw `Cannot set properties of null (setting 'textContent')`?
A: In `docs-mintlify/snippets/home-prompt-island.jsx`, reading `event.currentTarget` after `await navigator.clipboard.writeText(...)` can produce null in runtime event wrappers. Capture `const button = event.currentTarget` before awaiting.
Q: How should the docs Explore Apps grid support both light and dark themes?
A: In `docs-mintlify/snippets/docs-apps-home-grid.jsx`, use light-first container/tile styles with explicit `dark:*` overrides (including `dark:invert` for icons) so light mode remains readable while dark mode keeps the neon tile look.
Q: How can docs-mintlify add an Apps sidebar filter without React hooks?
A: In `docs-mintlify/snippets/docs-apps-home-grid.jsx`, inject a compact `<input>` under the sidebar "Apps" header via DOM (`#navigation-items` + `.sidebar-group-header` text match), filter that group's `<ul>` rows on `input`, and observe `document.documentElement.classList` with `MutationObserver` to swap light/dark inline styles when `html` toggles between `light` and `dark`.
Q: Why did Explore Apps look light in dark mode even with `dark:bg` set on the container?
A: `bg-gradient-to-b` applies a background image, and `dark:bg-[#...]` only changes background color, so the light gradient image stays visible. Use `dark:from[...] dark:to[...]` (or a full dark gradient/image override) so dark mode replaces the gradient itself.
Q: What should we do when changing docs sidebar search injection from block to inline?
A: Remove legacy `div[data-apps-sidebar-search='true']` nodes before adding the new inline header input; otherwise old and new filters can coexist after hot reload and render duplicate search boxes.
Q: What caused the Explore Apps hover layout shift?
A: The app link wrapper in `docs-mintlify/snippets/docs-apps-home-grid.jsx` used `hover:-translate-y-0.5`, which makes tiles physically move on hover and looks like layout jank. Removing the translate/transform from the wrapper keeps hover effects without perceived shifting.
Q: How should the sidebar Apps filter behave when there are no matches?
A: In `docs-mintlify/snippets/docs-apps-home-grid.jsx`, track visible rows while filtering and show a small inline empty state (`No more results. Clear filter`) when query is non-empty and visible count is zero; wire `Clear filter` to reset the input, rerun filtering, and refocus the input.

View File

@ -4,6 +4,9 @@ description: "Stack Auth documentation for setup, components, SDK usage, and RES
sidebarTitle: "Overview"
---
import { HomePromptIsland } from "/snippets/home-prompt-island.jsx";
import { DocsAppsHomeGrid } from "/snippets/docs-apps-home-grid.jsx";
export const SectionLink = ({ href, children }) => (
<a href={href} className="text-base font-semibold text-slate-900 no-underline hover:text-[#6b5df7] dark:text-white dark:hover:text-[#8b7cf9]">{children}</a>
);
@ -12,78 +15,7 @@ export const ChipLink = ({ href, children }) => (
<a href={href} className="inline-flex rounded-md bg-slate-100 px-2.5 py-1 text-xs font-medium text-slate-600 no-underline transition hover:bg-[#6b5df7]/10 hover:text-[#6b5df7] dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-[#6b5df7]/20 dark:hover:text-[#8b7cf9]">{children}</a>
);
export const CardTitle = ({ href, children }) => (
<a href={href} className="mb-1 block text-sm font-semibold text-slate-900 no-underline hover:text-[#6b5df7] dark:text-white dark:hover:text-[#8b7cf9]">{children}</a>
);
export const CardChip = ({ href, children }) => (
<a href={href} className="rounded bg-slate-100 px-2 py-0.5 text-xs text-slate-500 no-underline transition hover:bg-[#6b5df7]/10 hover:text-[#6b5df7] dark:bg-slate-800 dark:text-slate-400 dark:hover:bg-[#6b5df7]/20 dark:hover:text-[#8b7cf9]">{children}</a>
);
<div className="not-prose relative w-full overflow-hidden rounded-xl bg-[#121212]">
<img
src="/images/hero-illustration-full.svg"
alt=""
aria-hidden="true"
className="pointer-events-none absolute right-[-14rem] top-1/2 w-[980px] max-w-none -translate-y-1/2 opacity-80 sm:right-[-10rem] sm:w-[1080px] lg:right-[-6rem] lg:w-[1180px] dark:opacity-55"
/>
<div className="absolute inset-0 bg-gradient-to-r from-[#121212] via-[#121212]/78 to-[#121212]/28" />
<div className="relative mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8 lg:py-20">
<div className="flex flex-col justify-center">
<h1 className="mt-6 text-4xl font-bold tracking-tight text-white sm:text-5xl lg:text-6xl">
Ship auth without <br />
rebuilding the basics
</h1>
<p className="mt-5 max-w-2xl text-base leading-7 text-slate-300 sm:text-lg">
Learn how to set up Stack Auth, drop in prebuilt components, use the
SDK, and integrate any backend through the REST API.
</p>
<div className="mt-8 flex flex-wrap gap-3">
<a
href="/guides/getting-started/setup"
className="inline-flex items-center justify-center rounded-lg bg-[#6346ec] px-5 py-3 text-sm font-semibold text-white no-underline transition hover:bg-[#6346ec]/90"
>
Start setup
</a>
<a
href="/guides/getting-started/setup"
className="inline-flex items-center justify-center rounded-lg border border-slate-600 px-5 py-3 text-sm font-semibold text-slate-200 no-underline transition hover:border-[#6346ec] hover:text-white"
>
Browse docs
</a>
</div>
</div>
</div>
</div>
<div className="mx-auto mt-16 max-w-7xl px-4 sm:px-6 lg:px-8">
<div>
<h2 className="text-3xl font-bold tracking-tight text-slate-900 dark:text-white">Quick Start</h2>
<p className="mt-3 max-w-3xl text-base text-slate-600 dark:text-slate-300">
Go from a new project to authenticated users with the shortest path through the docs.
</p>
</div>
<Steps className="mt-8">
<Step title="Set up Stack Auth">
Follow the [setup guide](/guides/getting-started/setup) to initialize Stack Auth in your app.
```bash
npx @stackframe/stack-cli@latest init
```
</Step>
<Step title="Add auth UI">
Use the [setup guide](/guides/getting-started/setup) to configure prebuilt auth interfaces and handlers.
</Step>
<Step title="Read user data in code">
Reach for the [SDK overview](/sdk/overview) when you need hooks, objects, and typed access to users, teams, and sessions.
</Step>
<Step title="Go lower-level when needed">
Use the [REST API overview](/api/overview) for backend integrations, custom clients, and non-Next.js environments.
</Step>
</Steps>
</div>
<HomePromptIsland />
<div className="mx-auto mt-20 max-w-7xl px-4 sm:px-6 lg:px-8">
<div>
@ -109,81 +41,58 @@ export const CardChip = ({ href, children }) => (
</div>
</div>
{/* Components */}
{/* Explore Apps */}
<div className="relative mb-10">
<div className="absolute -left-[2.55rem] top-0.5 h-4 w-4 rounded-full border-2 border-[#6b5df7] bg-[#6b5df7]" />
<SectionLink href="/sdk/overview">Components</SectionLink>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">Drop-in UI for sign-in, sign-up, account settings, and team switching.</p>
<SectionLink href="/guides/apps/authentication/overview">Explore Apps</SectionLink>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
Open app guides from a home-screen style quick access grid, modeled after dashboard Overview.
</p>
<DocsAppsHomeGrid />
</div>
{/* Going Further */}
<div className="relative mb-10">
<div className="absolute -left-[2.55rem] top-0.5 h-4 w-4 rounded-full border-2 border-[#6b5df7] bg-[#6b5df7]" />
<SectionLink href="/guides/going-further/stack-app">Going Further</SectionLink>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
Configure Stack App deeply, integrate your backend, and use lower-level interfaces where needed.
</p>
<div className="mt-3 flex flex-wrap gap-2">
<ChipLink href="/guides/apps/authentication/overview">SignIn / SignUp</ChipLink>
<ChipLink href="/guides/going-further/stack-app">Stack App</ChipLink>
<ChipLink href="/guides/going-further/backend-integration">Backend Integration</ChipLink>
<ChipLink href="/guides/going-further/local-development">Local Development</ChipLink>
</div>
</div>
{/* SDK Reference */}
<div className="relative mb-4">
<div className="relative mb-10">
<div className="absolute -left-[2.55rem] top-0.5 h-4 w-4 rounded-full border-2 border-[#6b5df7] bg-[#6b5df7]" />
<SectionLink href="/sdk/overview">SDK Reference</SectionLink>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">Hooks, types, and objects for reading and writing user data in code.</p>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
Use hooks, objects, and types to read and write auth data directly in your app code.
</p>
<div className="mt-3 flex flex-wrap gap-2">
<ChipLink href="/sdk/hooks/use-user">useUser</ChipLink>
<ChipLink href="/sdk/types/user">Types</ChipLink>
<ChipLink href="/sdk/objects/stack-app">StackApp</ChipLink>
<ChipLink href="/sdk/types/user">User Type</ChipLink>
</div>
</div>
{/* REST API */}
<div className="relative mb-4">
<div className="absolute -left-[2.55rem] top-0.5 h-4 w-4 rounded-full border-2 border-[#6b5df7] bg-[#6b5df7]" />
<SectionLink href="/api/overview">REST API</SectionLink>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
Integrate Stack Auth from any backend or language through HTTP endpoints and webhook flows.
</p>
<div className="mt-3 flex flex-wrap gap-2">
<ChipLink href="/api/overview">Overview</ChipLink>
<ChipLink href="/guides/apps/webhooks/overview">Webhooks</ChipLink>
<ChipLink href="/guides/going-further/backend-integration">Backend Integration</ChipLink>
</div>
</div>
</div>
{/* Branch divider */}
<div className="ml-4 flex items-center gap-3 py-4 pl-8">
<div className="h-px flex-1 bg-slate-200 dark:bg-slate-700" />
<span className="text-xs font-medium uppercase tracking-wider text-slate-400 dark:text-slate-500">Then pick your path</span>
<div className="h-px flex-1 bg-slate-200 dark:bg-slate-700" />
</div>
{/* Branching cards */}
<div className="ml-4 grid grid-cols-1 gap-3 pl-8 sm:grid-cols-3">
<div className="rounded-lg border border-slate-200 p-4 dark:border-slate-700">
<CardTitle href="/guides/going-further/stack-app">Going Further</CardTitle>
<p className="text-xs text-slate-500 dark:text-slate-400">Stack App configuration, backend integration, local development, metadata.</p>
<div className="mt-2 flex flex-wrap gap-1.5">
<CardChip href="/guides/going-further/stack-app">Stack App</CardChip>
<CardChip href="/guides/going-further/local-development">Local dev</CardChip>
<CardChip href="/guides/going-further/backend-integration">Backend</CardChip>
</div>
</div>
<div className="rounded-lg border border-slate-200 p-4 dark:border-slate-700">
<CardTitle href="/guides/apps/authentication/overview">Apps</CardTitle>
<p className="text-xs text-slate-500 dark:text-slate-400">Authentication, emails, payments, webhooks, API keys.</p>
<div className="mt-2 flex flex-wrap gap-1.5">
<CardChip href="/guides/apps/authentication/overview">Auth</CardChip>
<CardChip href="/guides/apps/emails/overview">Emails</CardChip>
<CardChip href="/guides/apps/payments/overview">Payments</CardChip>
</div>
</div>
<div className="rounded-lg border border-slate-200 p-4 dark:border-slate-700">
<CardTitle href="/api/overview">REST API</CardTitle>
<p className="text-xs text-slate-500 dark:text-slate-400">HTTP endpoints for any backend or language.</p>
<div className="mt-2 flex flex-wrap gap-1.5">
<CardChip href="/api/overview">REST API</CardChip>
<CardChip href="/guides/apps/webhooks/overview">Webhooks</CardChip>
</div>
</div>
</div>
<h3 className="mt-10 mb-6 text-lg font-semibold text-slate-500 dark:text-slate-400">Explore Apps</h3>
<CardGroup cols={3}>
<Card title="Authentication" icon="right-to-bracket" href="/guides/apps/authentication/overview" />
<Card title="Auth Providers" icon="users" href="/guides/apps/authentication/auth-providers" />
<Card title="Emails" icon="envelope" href="/guides/apps/emails/overview" />
<Card title="Payments" icon="credit-card" href="/guides/apps/payments/overview" />
<Card title="Webhooks" icon="webhook" href="/guides/apps/webhooks/overview" />
<Card title="API Keys" icon="key" href="/guides/apps/api-keys/overview" />
<Card title="Permissions" icon="shield" href="/guides/apps/rbac/overview" />
<Card title="Teams" icon="people-group" href="/guides/apps/teams/overview" />
<Card title="Analytics" icon="chart-line" href="/guides/apps/analytics/overview" />
<Card title="Data Vault" icon="database" href="/guides/apps/data-vault/overview" />
<Card title="Launch Checklist" icon="clipboard-check" href="/guides/apps/launch-checklist/overview" />
</CardGroup>
</div>
<div className="mx-auto mt-20 max-w-7xl px-4 pb-16 sm:px-6 lg:px-8">

View File

@ -0,0 +1,258 @@
export const DocsAppsHomeGrid = () => {
const labelOverrides = new Map([
["api-keys", "API Keys"],
["rbac", "RBAC"],
]);
const toStartCase = (value) => {
const override = labelOverrides.get(value);
if (override != null) {
return override;
}
return value
.split("-")
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
};
const fallbackApps = [
{ name: "Authentication", href: "/guides/apps/authentication/overview", iconSrc: "/images/app-icons/authentication.svg" },
{ name: "Emails", href: "/guides/apps/emails/overview", iconSrc: "/images/app-icons/emails.svg" },
{ name: "Payments", href: "/guides/apps/payments/overview", iconSrc: "/images/app-icons/payments.svg" },
{ name: "Analytics", href: "/guides/apps/analytics/overview", iconSrc: "/images/app-icons/analytics.svg" },
{ name: "Teams", href: "/guides/apps/teams/overview", iconSrc: "/images/app-icons/teams.svg" },
{ name: "Fraud Protection", href: "/guides/apps/fraud-protection/overview", iconSrc: "/images/app-icons/fraud-protection.svg" },
{ name: "RBAC", href: "/guides/apps/rbac/overview", iconSrc: "/images/app-icons/rbac.svg" },
{ name: "API Keys", href: "/guides/apps/api-keys/overview", iconSrc: "/images/app-icons/api-keys.svg" },
{ name: "Data Vault", href: "/guides/apps/data-vault/overview", iconSrc: "/images/app-icons/data-vault.svg" },
{ name: "Webhooks", href: "/guides/apps/webhooks/overview", iconSrc: "/images/app-icons/webhooks.svg" },
{ name: "Launch Checklist", href: "/guides/apps/launch-checklist/overview", iconSrc: "/images/app-icons/launch-checklist.svg" },
];
const getAppsFromSidebar = () => {
if (typeof document === "undefined") {
return fallbackApps;
}
const sidebarRoot = document.querySelector("ul#sidebar-group");
if (sidebarRoot == null) {
return fallbackApps;
}
const candidateLinks = sidebarRoot.querySelectorAll('a[href^="/guides/apps/"]');
const appItems = [];
const seenHrefs = new Set();
for (const link of candidateLinks) {
const href = link.getAttribute("href");
if (href == null || seenHrefs.has(href)) {
continue;
}
const iconImage = link.querySelector("img");
if (iconImage == null) {
continue;
}
const textContent = link.textContent?.replace(/\s+/g, " ").trim();
const slug = href.replace(/^\/guides\/apps\//, "").split("/")[0];
const iconSrc = iconImage.getAttribute("src") ?? `/images/app-icons/${slug}.svg`;
const name = textContent != null && textContent.length > 0 ? textContent : toStartCase(slug);
seenHrefs.add(href);
appItems.push({ name, href, iconSrc });
}
if (appItems.length === 0) {
return fallbackApps;
}
return appItems;
};
const appLinks = getAppsFromSidebar();
const onExploreSearchInput = (event) => {
const input = event.currentTarget;
const root = input.closest("[data-explore-apps-root='true']");
if (root == null) {
return;
}
const query = input.value.trim().toLowerCase();
const cards = root.querySelectorAll("[data-explore-app-card='true']");
let visibleCount = 0;
for (const card of cards) {
const appName = (card.getAttribute("data-app-name") ?? "").toLowerCase();
const isVisible = query.length === 0 || appName.includes(query);
card.style.display = isVisible ? "" : "none";
if (isVisible) {
visibleCount += 1;
}
}
const emptyState = root.querySelector("[data-explore-app-empty='true']");
if (emptyState != null) {
emptyState.style.display = visibleCount === 0 ? "block" : "none";
}
};
if (typeof document !== "undefined" && typeof window !== "undefined") {
window.requestAnimationFrame(() => {
const navigationItems = document.querySelector("#navigation-items");
if (navigationItems == null) {
return;
}
const sidebarHeaders = navigationItems.querySelectorAll(".sidebar-group-header");
const appsHeader = Array.from(sidebarHeaders).find((header) => header.textContent?.trim() === "Apps");
if (appsHeader == null) {
return;
}
const appsGroupContainer = appsHeader.parentElement;
const appsList = appsGroupContainer?.querySelector("ul");
if (appsGroupContainer == null || appsList == null) {
return;
}
const existingHeaderSearch = appsHeader.querySelector("[data-apps-sidebar-search='true']");
if (existingHeaderSearch != null) {
existingHeaderSearch.remove();
}
const legacySearchContainers = appsGroupContainer.querySelectorAll("div[data-apps-sidebar-search='true']");
for (const legacySearchContainer of legacySearchContainers) {
legacySearchContainer.remove();
}
const existingEmptyStates = appsGroupContainer.querySelectorAll("[data-apps-sidebar-empty='true']");
for (const existingEmptyState of existingEmptyStates) {
existingEmptyState.remove();
}
const searchInput = document.createElement("input");
searchInput.type = "text";
searchInput.placeholder = "Filter...";
searchInput.setAttribute("aria-label", "Filter apps in sidebar");
searchInput.style.width = "120px";
searchInput.style.height = "24px";
searchInput.style.borderRadius = "7px";
searchInput.style.padding = "0 8px";
searchInput.style.fontSize = "11px";
searchInput.style.lineHeight = "1";
searchInput.style.outline = "none";
searchInput.style.transition = "border-color 150ms ease, background-color 150ms ease, color 150ms ease";
searchInput.style.fontWeight = "500";
searchInput.style.marginLeft = "auto";
searchInput.style.flexShrink = "0";
const emptyState = document.createElement("div");
emptyState.setAttribute("data-apps-sidebar-empty", "true");
emptyState.style.display = "none";
emptyState.style.padding = "2px 0 8px 16px";
emptyState.style.fontSize = "12px";
emptyState.style.lineHeight = "1.3";
const emptyStatePrefix = document.createElement("span");
emptyStatePrefix.textContent = "No more results. ";
emptyState.appendChild(emptyStatePrefix);
const clearFilterButton = document.createElement("button");
clearFilterButton.type = "button";
clearFilterButton.textContent = "Clear filter";
clearFilterButton.style.border = "none";
clearFilterButton.style.padding = "0";
clearFilterButton.style.background = "transparent";
clearFilterButton.style.fontSize = "12px";
clearFilterButton.style.fontWeight = "600";
clearFilterButton.style.cursor = "pointer";
clearFilterButton.style.textDecoration = "underline";
clearFilterButton.style.textUnderlineOffset = "2px";
emptyState.appendChild(clearFilterButton);
const applyTheme = () => {
const isDark = document.documentElement.classList.contains("dark");
searchInput.style.background = isDark ? "rgba(17,24,39,0.72)" : "rgba(248,250,252,0.98)";
searchInput.style.color = isDark ? "#e5e7eb" : "#111827";
searchInput.style.border = isDark ? "1px solid rgba(75,85,99,0.9)" : "1px solid rgba(203,213,225,0.95)";
emptyState.style.color = isDark ? "rgba(203,213,225,0.86)" : "rgba(55,65,81,0.9)";
clearFilterButton.style.color = isDark ? "#8fb7ff" : "#295fbe";
};
const filterSidebarApps = () => {
const query = searchInput.value.trim().toLowerCase();
const appRows = Array.from(appsList.children);
let visibleCount = 0;
for (const row of appRows) {
const searchableElement = row.querySelector("a, button") ?? row;
const rowText = searchableElement.textContent?.replace(/\s+/g, " ").trim().toLowerCase() ?? "";
const isVisible = query.length === 0 || rowText.includes(query);
row.style.display = isVisible ? "" : "none";
if (isVisible) {
visibleCount += 1;
}
}
emptyState.style.display = query.length > 0 && visibleCount === 0 ? "block" : "none";
};
clearFilterButton.addEventListener("click", () => {
searchInput.value = "";
filterSidebarApps();
searchInput.focus();
});
searchInput.addEventListener("input", filterSidebarApps);
applyTheme();
const classObserver = new MutationObserver(applyTheme);
classObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
appsHeader.style.display = "flex";
appsHeader.style.alignItems = "center";
appsHeader.style.gap = "8px";
appsHeader.style.paddingRight = "12px";
searchInput.setAttribute("data-apps-sidebar-search", "true");
appsHeader.appendChild(searchInput);
appsGroupContainer.insertBefore(emptyState, appsList);
});
}
return (
<div data-explore-apps-root="true" className="mt-4 rounded-2xl border border-[#d6e4ff] bg-gradient-to-b from-[#f7faff] to-[#eaf2ff] p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.9),0_10px_30px_-24px_rgba(47,79,140,0.35)] dark:border-[#1f2d45] dark:from-[#11203a] dark:to-[#070f1f] dark:shadow-[inset_0_1px_0_rgba(112,152,224,0.18),0_16px_34px_-24px_rgba(2,8,20,0.85)] sm:p-4">
<div className="mb-3 sm:mb-4">
<input
type="text"
placeholder="Search apps..."
aria-label="Search Explore Apps"
onInput={onExploreSearchInput}
className="h-10 w-full rounded-xl border border-[#b9cdf4] bg-white/90 px-3 text-sm text-[#1a2d52] outline-none transition-colors duration-150 focus:border-[#7ea6ed] dark:border-[#2b4a79] dark:bg-[#0c1627] dark:text-[#d6e5ff] dark:focus:border-[#4e84d8]"
/>
</div>
<div className="grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-4 sm:gap-x-3 sm:gap-y-4 lg:grid-cols-6">
{appLinks.map((appLink) => (
<a
key={appLink.name}
href={appLink.href}
data-explore-app-card="true"
data-app-name={appLink.name}
className="group flex flex-col items-center gap-2 px-1 py-0.5 no-underline"
title={appLink.name}
>
<div className="relative flex h-[84px] w-[84px] items-center justify-center overflow-hidden rounded-[18px] border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<img
src={appLink.iconSrc}
alt=""
aria-hidden="true"
className="h-[34px] w-[34px] opacity-80 brightness-0 transition-all duration-150 group-hover:transition-none group-hover:opacity-90 dark:invert dark:brightness-125 dark:opacity-95"
/>
</div>
<span
className="min-h-[2.2rem] max-w-[84px] text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title={appLink.name}
>
{appLink.name}
</span>
</a>
))}
</div>
<p data-explore-app-empty="true" className="mt-3 hidden text-center text-xs text-[#4a5f89] dark:text-[#8fa4cc]">
No apps match your search.
</p>
</div>
);
};

View File

@ -0,0 +1,66 @@
export const HomePromptIsland = () => {
const agentSetupPromptPlaceholder = `You are my coding agent.
Set up Stack Auth in this project.
- Install and configure Stack Auth
- Create initial authentication routes
- Add sign-in and sign-up UI
- Verify local development setup
Return the exact files changed and next steps.`;
const onCopy = async (event) => {
const button = event.currentTarget;
await navigator.clipboard.writeText(agentSetupPromptPlaceholder);
button.textContent = "Copied";
window.setTimeout(() => {
button.textContent = "Copy prompt";
}, 1300);
};
return (
<div className="not-prose my-6 rounded-3xl border border-[#d7dff6] bg-gradient-to-br from-[#f6f8ff] via-[#f2f5ff] to-[#edf7ff] p-5 text-zinc-900 shadow-[0_20px_60px_-40px_rgba(50,70,150,0.35)] sm:p-7 dark:border-[#2c3751] dark:bg-gradient-to-br dark:from-[#0c1423] dark:via-[#111b2f] dark:to-[#0b212e] dark:text-zinc-100 dark:shadow-[0_20px_60px_-40px_rgba(0,0,0,0.8)]">
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[#4f5f95] dark:text-[#8ea4d2]">
Agent-first setup
</p>
<h1 className="mt-3 text-4xl font-semibold tracking-tight text-zinc-900 sm:text-5xl dark:text-zinc-50">
Start with a single prompt.
</h1>
<p className="mt-3 max-w-3xl text-base leading-7 text-zinc-600 dark:text-zinc-300">
Set up Stack Auth by copying the prompt below into your favorite coding agent.
</p>
<div className="relative mt-6">
<textarea
readOnly
value={agentSetupPromptPlaceholder}
className="h-28 w-full resize-none overflow-hidden rounded-2xl border border-[#cdd7f4] bg-white/75 px-4 py-3 pr-32 font-mono text-xs leading-6 text-zinc-700 outline-none backdrop-blur-sm sm:text-sm dark:border-[#33476d] dark:bg-black/20 dark:text-zinc-200"
/>
<button
type="button"
onClick={onCopy}
className="absolute right-2 top-2 inline-flex items-center justify-center rounded-lg border border-[#9fb5e4] bg-[#eaf1ff] px-3 py-1.5 text-xs font-semibold text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 focus-visible:ring-offset-[#f3f6ff] dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51] dark:focus-visible:ring-offset-[#0f1a2e]"
>
Copy prompt
</button>
<div className="pointer-events-none absolute inset-x-2 bottom-2 h-8 rounded-b-xl bg-gradient-to-t from-[#f4f7ff] to-transparent dark:from-[#0f1a2e]" />
</div>
<div className="mt-5 flex flex-col gap-3 sm:flex-row">
<a
href="https://app.stack-auth.com"
className="inline-flex items-center justify-center rounded-xl bg-[#1e2f57] px-5 py-3 text-sm font-semibold !text-[#eef4ff] no-underline transition-colors duration-150 hover:transition-none hover:bg-[#253a6b] dark:bg-[#1e2f57] dark:hover:bg-[#253a6b] dark:!text-[#eef4ff]"
>
Go to dashboard
</a>
<a
href="/guides/getting-started/setup"
className="inline-flex items-center justify-center rounded-xl border border-[#9fb5e4] bg-white/60 px-5 py-3 text-sm font-semibold text-[#1f3764] no-underline transition-colors duration-150 hover:transition-none hover:bg-white/85 dark:border-[#3d5a91] dark:bg-transparent dark:text-[#d7e7ff] dark:hover:bg-white/10"
>
Manual installation instructions
</a>
</div>
</div>
);
};