From 34c3117ed8ed24a65a6eaa1ed5396ad26521e5ba Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 19 Aug 2026 13:56:16 -0700 Subject: [PATCH 1/2] feat(nav): group KMP and Web into the SDK navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the KMP and Web tabs into the desktop SDK dropdown, and give the mobile sidebar dropdown Docs/SDKs sections with the SDKs grouped last. Tag both SDKs as Beta like Unity, add their missing cards to the home page grid, and fill the KMP/Web gaps in the llms.txt, OG label, and structured-data section maps — including the four missing /docs/{kmp,web}/llms{,-full}.txt route files those maps advertise. --- content/docs/kmp/meta.json | 1 + content/docs/web/meta.json | 1 + src/components/DocsHeader.tsx | 26 +- src/components/DocsSidebar.tsx | 528 ++++++++++++++++++++++++++++++ src/lib/llms.ts | 12 +- src/lib/og.shared.ts | 2 + src/lib/sdk-tabs.ts | 31 ++ src/lib/structured-data.ts | 2 + src/routeTree.gen.ts | 84 +++++ src/routes/$.tsx | 18 +- src/routes/index.tsx | 12 + src/routes/kmp/llms-full[.]txt.ts | 10 + src/routes/kmp/llms[.]txt.ts | 10 + src/routes/web/llms-full[.]txt.ts | 10 + src/routes/web/llms[.]txt.ts | 10 + 15 files changed, 730 insertions(+), 27 deletions(-) create mode 100644 src/components/DocsSidebar.tsx create mode 100644 src/lib/sdk-tabs.ts create mode 100644 src/routes/kmp/llms-full[.]txt.ts create mode 100644 src/routes/kmp/llms[.]txt.ts create mode 100644 src/routes/web/llms-full[.]txt.ts create mode 100644 src/routes/web/llms[.]txt.ts diff --git a/content/docs/kmp/meta.json b/content/docs/kmp/meta.json index afa95270..42b7ba93 100644 --- a/content/docs/kmp/meta.json +++ b/content/docs/kmp/meta.json @@ -1,5 +1,6 @@ { "title": "KMP SDK", + "description": "Beta", "icon": "Layers", "root": true, "pages": [ diff --git a/content/docs/web/meta.json b/content/docs/web/meta.json index 64da908d..ffc3f161 100644 --- a/content/docs/web/meta.json +++ b/content/docs/web/meta.json @@ -1,5 +1,6 @@ { "title": "Web SDK", + "description": "Beta", "icon": "Globe", "root": true, "pages": [ diff --git a/src/components/DocsHeader.tsx b/src/components/DocsHeader.tsx index 0ecac2fd..6d0fa9fe 100644 --- a/src/components/DocsHeader.tsx +++ b/src/components/DocsHeader.tsx @@ -1,6 +1,7 @@ "use client"; import { cn } from "@/lib/cn"; +import { getSdkTabLabel, isSdkTab } from "@/lib/sdk-tabs"; import { buttonVariants } from "fumadocs-ui/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "fumadocs-ui/components/ui/popover"; import { @@ -15,31 +16,6 @@ import Link from "fumadocs-core/link"; import { ChevronDown, Languages, PanelLeft } from "lucide-react"; import { Fragment, useMemo, useRef, useState, type ComponentProps, type PointerEvent } from "react"; -const SDK_TAB_LABELS: Record = { - ios: "iOS", - android: "Android", - expo: "Expo", - flutter: "Flutter", - unity: "Unity", - "react-native": "React Native", - community: "Community", -}; - -const SDK_TAB_SLUGS = new Set(Object.keys(SDK_TAB_LABELS)); - -function getTabPrefix(url: string) { - return url.replace(/^\/docs\//, "").split("/")[0] ?? ""; -} - -function isSdkTab(tab: LayoutTab) { - return SDK_TAB_SLUGS.has(getTabPrefix(tab.url)); -} - -function getSdkTabLabel(tab: LayoutTab) { - const prefix = getTabPrefix(tab.url); - return SDK_TAB_LABELS[prefix] ?? String(tab.title); -} - function getSdkTabDescription(tab: LayoutTab) { return tab.description; } diff --git a/src/components/DocsSidebar.tsx b/src/components/DocsSidebar.tsx new file mode 100644 index 00000000..65ba9e64 --- /dev/null +++ b/src/components/DocsSidebar.tsx @@ -0,0 +1,528 @@ +"use client"; + +/** + * Adapted from `fumadocs-ui/layouts/notebook/slots/sidebar`, so the tabs dropdown can + * group SDK tabs into their own section at the end of the list. + */ + +import { cn } from "@/lib/cn"; +import { isSdkTab } from "@/lib/sdk-tabs"; +import { buttonVariants } from "fumadocs-ui/components/ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "fumadocs-ui/components/ui/popover"; +import { + SidebarContent as BaseSidebarContent, + SidebarFolder as BaseSidebarFolder, + SidebarFolderContent as BaseSidebarFolderContent, + SidebarFolderLink as BaseSidebarFolderLink, + SidebarFolderTrigger as BaseSidebarFolderTrigger, + SidebarItem as BaseSidebarItem, + SidebarSeparator as BaseSidebarSeparator, + SidebarDrawerContent, + SidebarDrawerOverlay, + SidebarViewport, + useFolder, + useFolderDepth, + useSidebar, +} from "fumadocs-ui/components/sidebar/base"; +import { createLinkItemRenderer } from "fumadocs-ui/components/sidebar/link-item"; +import { createPageTreeRenderer } from "fumadocs-ui/components/sidebar/page-tree"; +import { useNotebookLayout } from "fumadocs-ui/layouts/notebook"; +import { SidebarCollapseTrigger, SidebarTrigger } from "fumadocs-ui/layouts/notebook/slots/sidebar"; +import type { SidebarProps } from "fumadocs-ui/layouts/notebook/slots/sidebar"; +import { isLayoutTabActive, LinkItem, type LayoutTab } from "fumadocs-ui/layouts/shared"; +import { usePathname } from "fumadocs-core/framework"; +import Link from "fumadocs-core/link"; +import { cva } from "class-variance-authority"; +import { Check, ChevronsUpDown, Languages, SidebarIcon, X } from "lucide-react"; +import { + createElement, + Fragment, + useMemo, + useRef, + useState, + type ComponentProps, + type Ref, +} from "react"; + +const itemVariants = cva( + "relative flex flex-row items-center gap-2 rounded-lg p-2 text-start text-fd-muted-foreground wrap-anywhere [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + link: "transition-colors hover:bg-fd-accent/50 hover:text-fd-accent-foreground/80 hover:transition-none data-[active=true]:bg-fd-primary/10 data-[active=true]:text-fd-primary data-[active=true]:hover:transition-colors", + button: + "transition-colors hover:bg-fd-accent/50 hover:text-fd-accent-foreground/80 hover:transition-none", + }, + highlight: { + true: "data-[active=true]:before:content-[''] data-[active=true]:before:bg-fd-primary data-[active=true]:before:absolute data-[active=true]:before:w-px data-[active=true]:before:inset-y-2.5 data-[active=true]:before:start-2.5", + }, + }, + }, +); + +function getItemOffset(depth: number) { + return `calc(${2 + 3 * depth} * var(--spacing))`; +} + +function mergeRefs(...refs: (Ref | undefined)[]) { + return (value: T | null) => { + for (const ref of refs) { + if (typeof ref === "function") ref(value); + else if (ref) (ref as { current: T | null }).current = value; + } + }; +} + +function SidebarContent({ ref: refProp, className, children, ...props }: ComponentProps<"aside">) { + const { + props: { nav }, + } = useNotebookLayout(); + const navMode = nav?.mode ?? "auto"; + const ref = useRef(null); + + return ( + + {({ collapsed, hovered, ref: asideRef, ...rest }) => ( +
+ {collapsed &&
} + +
+ )} + + ); +} + +function SidebarDrawer({ children, className, ...props }: ComponentProps<"aside">) { + return ( + <> + + + {children} + + + ); +} + +function SidebarFolder(props: ComponentProps) { + return ; +} + +function SidebarSeparator({ + className, + style, + children, + ...props +}: ComponentProps) { + const depth = useFolderDepth(); + + return ( + + {children} + + ); +} + +function SidebarItem({ + className, + style, + children, + ...props +}: ComponentProps) { + const depth = useFolderDepth(); + + return ( + = 1 }), className)} + style={{ paddingInlineStart: getItemOffset(depth), ...style }} + {...props} + > + {children} + + ); +} + +function SidebarFolderTrigger({ + className, + style, + ...props +}: ComponentProps) { + const { depth, collapsible } = useFolder() ?? { depth: 1, collapsible: true }; + + return ( + + {props.children} + + ); +} + +function SidebarFolderLink({ + className, + style, + ...props +}: ComponentProps) { + const depth = useFolderDepth(); + + return ( + 1 }), "w-full", className)} + style={{ paddingInlineStart: getItemOffset(depth - 1), ...style }} + {...props} + > + {props.children} + + ); +} + +function SidebarFolderContent({ + className, + children, + ...props +}: ComponentProps) { + return ( + +
{children}
+
+ ); +} + +const SidebarPageTree = createPageTreeRenderer({ + SidebarFolder, + SidebarFolderContent, + SidebarFolderLink, + SidebarFolderTrigger, + SidebarItem, + SidebarSeparator, +}); + +const SidebarLinkItem = createLinkItemRenderer({ + SidebarFolder, + SidebarFolderContent, + SidebarFolderLink, + SidebarFolderTrigger, + SidebarItem, +}); + +export function DocsSidebar({ + banner, + footer, + components, + collapsible = true, + ...rest +}: SidebarProps) { + const { + menuItems, + slots, + props: { nav, tabs, tabMode }, + } = useNotebookLayout(); + const navMode = nav?.mode ?? "auto"; + const iconLinks = menuItems.filter((item) => item.type === "icon"); + + function renderHeader(props: ComponentProps<"div">) { + if (typeof banner === "function") return createElement(banner, props); + + return ( +
+ {props.children} + {banner} +
+ ); + } + + function renderFooter(props: ComponentProps<"div">) { + if (typeof footer === "function") return createElement(footer, props); + + return ( +
+ {props.children} + {footer} +
+ ); + } + + const viewport = ( + + {menuItems + .filter((item) => item.type !== "icon") + .map((item, i, arr) => ( + + ))} + + + ); + + return ( + <> + + {renderHeader({ + children: ( + <> + {navMode === "auto" && ( +
+ {slots.navTitle && ( + + )} + {nav?.children} + {collapsible && ( + + + + )} +
+ )} + {tabs.length > 0 && ( + + )} + + ), + })} + {viewport} + {renderFooter({ + className: cn( + "hidden flex-row text-fd-muted-foreground items-center border-t px-4 py-2.5", + iconLinks.length > 0 && "max-lg:flex", + ), + children: iconLinks.map((item, i) => ( + + {item.icon} + + )), + })} +
+ + {renderHeader({ + children: ( + <> + + + + {tabs.length > 0 && } + + ), + })} + {viewport} + {renderFooter({ + className: cn( + "hidden flex-row text-fd-muted-foreground items-center border-t p-4 pt-2 justify-end", + (slots.languageSelect || slots.themeSwitch) && "flex", + iconLinks.length > 0 && "max-lg:flex", + ), + children: ( + <> + {iconLinks.map((item, i) => ( + + {item.icon} + + ))} + {slots.languageSelect && ( + + + + )} + {slots.themeSwitch && } + + ), + })} + + + ); +} + +type TabSection = { title: string; options: LayoutTab[] }; + +function groupTabs(options: LayoutTab[]): TabSection[] { + const sections: TabSection[] = [ + { title: "Docs", options: options.filter((option) => !isSdkTab(option)) }, + { title: "SDKs", options: options.filter(isSdkTab) }, + ]; + + return sections.filter((section) => section.options.length > 0); +} + +function SidebarTabsDropdown({ + options, + placeholder, + ...props +}: ComponentProps<"button"> & { options: LayoutTab[]; placeholder?: React.ReactNode }) { + const [open, setOpen] = useState(false); + const { closeOnRedirect } = useSidebar(); + const pathname = usePathname(); + const selected = useMemo( + () => options.findLast((item) => isLayoutTabActive(item, pathname)), + [options, pathname], + ); + const sections = useMemo(() => groupTabs(options), [options]); + + const onClick = () => { + closeOnRedirect.current = false; + setOpen(false); + }; + + const item = selected ? ( + <> +
{selected.icon}
+
+

{selected.title}

+

+ {selected.description} +

+
+ + ) : ( + placeholder + ); + + return ( + + {item && ( + + {item} + + + )} + + {sections.map((section) => ( + +

+ {section.title} +

+ {section.options.map((option) => { + const isActive = selected && option.url === selected.url; + if (!isActive && option.unlisted) return null; + + return ( + +
+ {option.icon} +
+
+

{option.title}

+

+ {option.description} +

+
+ + + ); + })} +
+ ))} +
+
+ ); +} diff --git a/src/lib/llms.ts b/src/lib/llms.ts index 8470d8ec..9b89492a 100644 --- a/src/lib/llms.ts +++ b/src/lib/llms.ts @@ -25,10 +25,18 @@ export const llmsSectionConfigs = { label: "Expo", urlPrefix: "/docs/expo", }, + kmp: { + label: "KMP", + urlPrefix: "/docs/kmp", + }, unity: { label: "Unity", urlPrefix: "/docs/unity", }, + web: { + label: "Web", + urlPrefix: "/docs/web", + }, "react-native": { label: "React Native", urlPrefix: "/docs/react-native", @@ -90,7 +98,9 @@ function getExamplePathForSection(section: string): string { android: "/docs/android/guides/advanced/game-controller-support.md", flutter: "/docs/flutter/guides/advanced/game-controller-support.md", expo: "/docs/expo/guides/advanced/game-controller-support.md", + kmp: "/docs/kmp/guides/platform-differences.md", unity: "/docs/unity/guides/game-controller-input.md", + web: "/docs/web/guides/server-side-gating.md", "react-native": "/docs/react-native/guides/advanced/game-controller-support.md", dashboard: "/docs/dashboard/dashboard-settings/overview-settings-apple-search-ads.md", agents: "/docs/agents/create-an-agent.md", @@ -107,7 +117,7 @@ export function buildLLMSummaryTextFromPages(pages: LLMPageLike[], section?: str // Add Superwall description lines.push( - "Superwall is a platform for remotely configuring and A/B testing mobile app paywalls — no app update required. It provides SDKs for iOS, Android, Flutter, Expo, Unity, and React Native, plus a dashboard for managing paywall presentation, experiments, and subscription analytics.", + "Superwall is a platform for remotely configuring and A/B testing mobile app paywalls — no app update required. It provides SDKs for iOS, Android, Flutter, Expo, KMP, Unity, Web, and React Native, plus a dashboard for managing paywall presentation, experiments, and subscription analytics.", "", ); diff --git a/src/lib/og.shared.ts b/src/lib/og.shared.ts index 62de41a4..c6ed0729 100644 --- a/src/lib/og.shared.ts +++ b/src/lib/og.shared.ts @@ -25,11 +25,13 @@ const SECTION_LABELS: Record = { flutter: "Flutter", integrations: "Integrations", ios: "iOS", + kmp: "KMP", legacy: "Legacy", "react-native": "React Native", sdk: "SDKs", support: "Support", unity: "Unity", + web: "Web", "web-checkout": "Web Checkout", }; diff --git a/src/lib/sdk-tabs.ts b/src/lib/sdk-tabs.ts new file mode 100644 index 00000000..a9f467e0 --- /dev/null +++ b/src/lib/sdk-tabs.ts @@ -0,0 +1,31 @@ +import type { LayoutTab } from "fumadocs-ui/layouts/shared"; + +/** + * Tabs that belong to the grouped SDK navigation: a dropdown on the desktop navbar, + * an "SDKs" section at the end of the mobile sidebar dropdown. + */ +const SDK_TAB_LABELS: Record = { + ios: "iOS", + android: "Android", + expo: "Expo", + flutter: "Flutter", + kmp: "KMP", + unity: "Unity", + web: "Web", + "react-native": "React Native", + community: "Community", +}; + +const SDK_TAB_SLUGS = new Set(Object.keys(SDK_TAB_LABELS)); + +function getTabPrefix(url: string) { + return url.replace(/^\/docs\//, "").split("/")[0] ?? ""; +} + +export function isSdkTab(tab: LayoutTab) { + return SDK_TAB_SLUGS.has(getTabPrefix(tab.url)); +} + +export function getSdkTabLabel(tab: LayoutTab) { + return SDK_TAB_LABELS[getTabPrefix(tab.url)] ?? String(tab.title); +} diff --git a/src/lib/structured-data.ts b/src/lib/structured-data.ts index 35dc4080..3a69e929 100644 --- a/src/lib/structured-data.ts +++ b/src/lib/structured-data.ts @@ -54,7 +54,9 @@ const SECTION_NAMES_BY_SLUG: Record = { android: "Android SDK", expo: "Expo SDK", flutter: "Flutter SDK", + kmp: "KMP SDK", unity: "Unity SDK", + web: "Web SDK", "react-native": "React Native SDK", community: "Community SDKs", }; diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index a8e45ada..e9a798a1 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -22,6 +22,8 @@ import { Route as LlmsFullChar123sectionChar125DottxtRouteImport } from './route import { Route as SplatRouteImport } from './routes/$' import { Route as IndexRouteImport } from './routes/index' import { Route as SdkIndexRouteImport } from './routes/sdk/index' +import { Route as WebLlmsDottxtRouteImport } from './routes/web/llms[.]txt' +import { Route as WebLlmsFullDottxtRouteImport } from './routes/web/llms-full[.]txt' import { Route as WebCheckoutLlmsDottxtRouteImport } from './routes/web-checkout/llms[.]txt' import { Route as WebCheckoutLlmsFullDottxtRouteImport } from './routes/web-checkout/llms-full[.]txt' import { Route as UnityLlmsDottxtRouteImport } from './routes/unity/llms[.]txt' @@ -32,6 +34,8 @@ import { Route as SdkSplatRouteImport } from './routes/sdk/$' import { Route as ReactNativeLlmsDottxtRouteImport } from './routes/react-native/llms[.]txt' import { Route as ReactNativeLlmsFullDottxtRouteImport } from './routes/react-native/llms-full[.]txt' import { Route as LlmsDotmdxDocsRouteImport } from './routes/llms[.]mdx.docs' +import { Route as KmpLlmsDottxtRouteImport } from './routes/kmp/llms[.]txt' +import { Route as KmpLlmsFullDottxtRouteImport } from './routes/kmp/llms-full[.]txt' import { Route as IosLlmsDottxtRouteImport } from './routes/ios/llms[.]txt' import { Route as IosLlmsFullDottxtRouteImport } from './routes/ios/llms-full[.]txt' import { Route as IntegrationsLlmsDottxtRouteImport } from './routes/integrations/llms[.]txt' @@ -119,6 +123,16 @@ const SdkIndexRoute = SdkIndexRouteImport.update({ path: '/sdk/', getParentRoute: () => rootRouteImport, } as any) +const WebLlmsDottxtRoute = WebLlmsDottxtRouteImport.update({ + id: '/web/llms.txt', + path: '/web/llms.txt', + getParentRoute: () => rootRouteImport, +} as any) +const WebLlmsFullDottxtRoute = WebLlmsFullDottxtRouteImport.update({ + id: '/web/llms-full.txt', + path: '/web/llms-full.txt', + getParentRoute: () => rootRouteImport, +} as any) const WebCheckoutLlmsDottxtRoute = WebCheckoutLlmsDottxtRouteImport.update({ id: '/web-checkout/llms.txt', path: '/web-checkout/llms.txt', @@ -171,6 +185,16 @@ const LlmsDotmdxDocsRoute = LlmsDotmdxDocsRouteImport.update({ path: '/llms.mdx/docs', getParentRoute: () => rootRouteImport, } as any) +const KmpLlmsDottxtRoute = KmpLlmsDottxtRouteImport.update({ + id: '/kmp/llms.txt', + path: '/kmp/llms.txt', + getParentRoute: () => rootRouteImport, +} as any) +const KmpLlmsFullDottxtRoute = KmpLlmsFullDottxtRouteImport.update({ + id: '/kmp/llms-full.txt', + path: '/kmp/llms-full.txt', + getParentRoute: () => rootRouteImport, +} as any) const IosLlmsDottxtRoute = IosLlmsDottxtRouteImport.update({ id: '/ios/llms.txt', path: '/ios/llms.txt', @@ -297,6 +321,8 @@ export interface FileRoutesByFullPath { '/integrations/llms.txt': typeof IntegrationsLlmsDottxtRoute '/ios/llms-full.txt': typeof IosLlmsFullDottxtRoute '/ios/llms.txt': typeof IosLlmsDottxtRoute + '/kmp/llms-full.txt': typeof KmpLlmsFullDottxtRoute + '/kmp/llms.txt': typeof KmpLlmsDottxtRoute '/llms.mdx/docs': typeof LlmsDotmdxDocsRouteWithChildren '/react-native/llms-full.txt': typeof ReactNativeLlmsFullDottxtRoute '/react-native/llms.txt': typeof ReactNativeLlmsDottxtRoute @@ -307,6 +333,8 @@ export interface FileRoutesByFullPath { '/unity/llms.txt': typeof UnityLlmsDottxtRoute '/web-checkout/llms-full.txt': typeof WebCheckoutLlmsFullDottxtRoute '/web-checkout/llms.txt': typeof WebCheckoutLlmsDottxtRoute + '/web/llms-full.txt': typeof WebLlmsFullDottxtRoute + '/web/llms.txt': typeof WebLlmsDottxtRoute '/sdk/': typeof SdkIndexRoute '/api/raw/$': typeof ApiRawSplatRoute '/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute @@ -341,6 +369,8 @@ export interface FileRoutesByTo { '/integrations/llms.txt': typeof IntegrationsLlmsDottxtRoute '/ios/llms-full.txt': typeof IosLlmsFullDottxtRoute '/ios/llms.txt': typeof IosLlmsDottxtRoute + '/kmp/llms-full.txt': typeof KmpLlmsFullDottxtRoute + '/kmp/llms.txt': typeof KmpLlmsDottxtRoute '/react-native/llms-full.txt': typeof ReactNativeLlmsFullDottxtRoute '/react-native/llms.txt': typeof ReactNativeLlmsDottxtRoute '/sdk/$': typeof SdkSplatRoute @@ -350,6 +380,8 @@ export interface FileRoutesByTo { '/unity/llms.txt': typeof UnityLlmsDottxtRoute '/web-checkout/llms-full.txt': typeof WebCheckoutLlmsFullDottxtRoute '/web-checkout/llms.txt': typeof WebCheckoutLlmsDottxtRoute + '/web/llms-full.txt': typeof WebLlmsFullDottxtRoute + '/web/llms.txt': typeof WebLlmsDottxtRoute '/sdk': typeof SdkIndexRoute '/api/raw/$': typeof ApiRawSplatRoute '/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute @@ -385,6 +417,8 @@ export interface FileRoutesById { '/integrations/llms.txt': typeof IntegrationsLlmsDottxtRoute '/ios/llms-full.txt': typeof IosLlmsFullDottxtRoute '/ios/llms.txt': typeof IosLlmsDottxtRoute + '/kmp/llms-full.txt': typeof KmpLlmsFullDottxtRoute + '/kmp/llms.txt': typeof KmpLlmsDottxtRoute '/llms.mdx/docs': typeof LlmsDotmdxDocsRouteWithChildren '/react-native/llms-full.txt': typeof ReactNativeLlmsFullDottxtRoute '/react-native/llms.txt': typeof ReactNativeLlmsDottxtRoute @@ -395,6 +429,8 @@ export interface FileRoutesById { '/unity/llms.txt': typeof UnityLlmsDottxtRoute '/web-checkout/llms-full.txt': typeof WebCheckoutLlmsFullDottxtRoute '/web-checkout/llms.txt': typeof WebCheckoutLlmsDottxtRoute + '/web/llms-full.txt': typeof WebLlmsFullDottxtRoute + '/web/llms.txt': typeof WebLlmsDottxtRoute '/sdk/': typeof SdkIndexRoute '/api/raw/$': typeof ApiRawSplatRoute '/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute @@ -431,6 +467,8 @@ export interface FileRouteTypes { | '/integrations/llms.txt' | '/ios/llms-full.txt' | '/ios/llms.txt' + | '/kmp/llms-full.txt' + | '/kmp/llms.txt' | '/llms.mdx/docs' | '/react-native/llms-full.txt' | '/react-native/llms.txt' @@ -441,6 +479,8 @@ export interface FileRouteTypes { | '/unity/llms.txt' | '/web-checkout/llms-full.txt' | '/web-checkout/llms.txt' + | '/web/llms-full.txt' + | '/web/llms.txt' | '/sdk/' | '/api/raw/$' | '/llms.mdx/docs/$' @@ -475,6 +515,8 @@ export interface FileRouteTypes { | '/integrations/llms.txt' | '/ios/llms-full.txt' | '/ios/llms.txt' + | '/kmp/llms-full.txt' + | '/kmp/llms.txt' | '/react-native/llms-full.txt' | '/react-native/llms.txt' | '/sdk/$' @@ -484,6 +526,8 @@ export interface FileRouteTypes { | '/unity/llms.txt' | '/web-checkout/llms-full.txt' | '/web-checkout/llms.txt' + | '/web/llms-full.txt' + | '/web/llms.txt' | '/sdk' | '/api/raw/$' | '/llms.mdx/docs/$' @@ -518,6 +562,8 @@ export interface FileRouteTypes { | '/integrations/llms.txt' | '/ios/llms-full.txt' | '/ios/llms.txt' + | '/kmp/llms-full.txt' + | '/kmp/llms.txt' | '/llms.mdx/docs' | '/react-native/llms-full.txt' | '/react-native/llms.txt' @@ -528,6 +574,8 @@ export interface FileRouteTypes { | '/unity/llms.txt' | '/web-checkout/llms-full.txt' | '/web-checkout/llms.txt' + | '/web/llms-full.txt' + | '/web/llms.txt' | '/sdk/' | '/api/raw/$' | '/llms.mdx/docs/$' @@ -563,6 +611,8 @@ export interface RootRouteChildren { IntegrationsLlmsDottxtRoute: typeof IntegrationsLlmsDottxtRoute IosLlmsFullDottxtRoute: typeof IosLlmsFullDottxtRoute IosLlmsDottxtRoute: typeof IosLlmsDottxtRoute + KmpLlmsFullDottxtRoute: typeof KmpLlmsFullDottxtRoute + KmpLlmsDottxtRoute: typeof KmpLlmsDottxtRoute LlmsDotmdxDocsRoute: typeof LlmsDotmdxDocsRouteWithChildren ReactNativeLlmsFullDottxtRoute: typeof ReactNativeLlmsFullDottxtRoute ReactNativeLlmsDottxtRoute: typeof ReactNativeLlmsDottxtRoute @@ -573,6 +623,8 @@ export interface RootRouteChildren { UnityLlmsDottxtRoute: typeof UnityLlmsDottxtRoute WebCheckoutLlmsFullDottxtRoute: typeof WebCheckoutLlmsFullDottxtRoute WebCheckoutLlmsDottxtRoute: typeof WebCheckoutLlmsDottxtRoute + WebLlmsFullDottxtRoute: typeof WebLlmsFullDottxtRoute + WebLlmsDottxtRoute: typeof WebLlmsDottxtRoute SdkIndexRoute: typeof SdkIndexRoute ApiRawSplatRoute: typeof ApiRawSplatRoute } @@ -670,6 +722,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SdkIndexRouteImport parentRoute: typeof rootRouteImport } + '/web/llms.txt': { + id: '/web/llms.txt' + path: '/web/llms.txt' + fullPath: '/web/llms.txt' + preLoaderRoute: typeof WebLlmsDottxtRouteImport + parentRoute: typeof rootRouteImport + } + '/web/llms-full.txt': { + id: '/web/llms-full.txt' + path: '/web/llms-full.txt' + fullPath: '/web/llms-full.txt' + preLoaderRoute: typeof WebLlmsFullDottxtRouteImport + parentRoute: typeof rootRouteImport + } '/web-checkout/llms.txt': { id: '/web-checkout/llms.txt' path: '/web-checkout/llms.txt' @@ -740,6 +806,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LlmsDotmdxDocsRouteImport parentRoute: typeof rootRouteImport } + '/kmp/llms.txt': { + id: '/kmp/llms.txt' + path: '/kmp/llms.txt' + fullPath: '/kmp/llms.txt' + preLoaderRoute: typeof KmpLlmsDottxtRouteImport + parentRoute: typeof rootRouteImport + } + '/kmp/llms-full.txt': { + id: '/kmp/llms-full.txt' + path: '/kmp/llms-full.txt' + fullPath: '/kmp/llms-full.txt' + preLoaderRoute: typeof KmpLlmsFullDottxtRouteImport + parentRoute: typeof rootRouteImport + } '/ios/llms.txt': { id: '/ios/llms.txt' path: '/ios/llms.txt' @@ -920,6 +1000,8 @@ const rootRouteChildren: RootRouteChildren = { IntegrationsLlmsDottxtRoute: IntegrationsLlmsDottxtRoute, IosLlmsFullDottxtRoute: IosLlmsFullDottxtRoute, IosLlmsDottxtRoute: IosLlmsDottxtRoute, + KmpLlmsFullDottxtRoute: KmpLlmsFullDottxtRoute, + KmpLlmsDottxtRoute: KmpLlmsDottxtRoute, LlmsDotmdxDocsRoute: LlmsDotmdxDocsRouteWithChildren, ReactNativeLlmsFullDottxtRoute: ReactNativeLlmsFullDottxtRoute, ReactNativeLlmsDottxtRoute: ReactNativeLlmsDottxtRoute, @@ -930,6 +1012,8 @@ const rootRouteChildren: RootRouteChildren = { UnityLlmsDottxtRoute: UnityLlmsDottxtRoute, WebCheckoutLlmsFullDottxtRoute: WebCheckoutLlmsFullDottxtRoute, WebCheckoutLlmsDottxtRoute: WebCheckoutLlmsDottxtRoute, + WebLlmsFullDottxtRoute: WebLlmsFullDottxtRoute, + WebLlmsDottxtRoute: WebLlmsDottxtRoute, SdkIndexRoute: SdkIndexRoute, ApiRawSplatRoute: ApiRawSplatRoute, } diff --git a/src/routes/$.tsx b/src/routes/$.tsx index d746f0bf..789dfcdf 100644 --- a/src/routes/$.tsx +++ b/src/routes/$.tsx @@ -1,5 +1,11 @@ import { createFileRoute, notFound } from "@tanstack/react-router"; import { DocsLayout } from "fumadocs-ui/layouts/notebook"; +import { + SidebarCollapseTrigger, + SidebarProvider, + SidebarTrigger, + useSidebar, +} from "fumadocs-ui/layouts/notebook/slots/sidebar"; import { getLayoutTabs, type LayoutTab } from "fumadocs-ui/layouts/shared"; import type { Root as PageTreeRoot } from "fumadocs-core/page-tree"; import { createServerFn } from "@tanstack/react-start"; @@ -16,6 +22,7 @@ import { Feedback } from "@/components/feedback/client"; import type { ActionResponse, PageFeedback } from "@/components/feedback/schema"; import { LoginStatusProvider } from "@/components/LoginStatusContext"; import { DocsHeader } from "@/components/DocsHeader"; +import { DocsSidebar } from "@/components/DocsSidebar"; import { buildCanonicalUrl, buildSocialImageUrl, @@ -274,7 +281,16 @@ function Page() { prefetch: true, footer: , }} - slots={{ header: DocsHeader }} + slots={{ + header: DocsHeader, + sidebar: { + provider: SidebarProvider, + root: DocsSidebar, + trigger: SidebarTrigger, + collapseTrigger: SidebarCollapseTrigger, + useSidebar, + }, + }} > {clientLoader.useContent(data.path, data)} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 10f25023..0778fb98 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -146,12 +146,24 @@ const sdkCards: DocCard[] = [ href: buildDocsPath("flutter"), icon: , }, + { + title: "KMP (Beta)", + description: "Integrate Superwall into your Kotlin Multiplatform app.", + href: buildDocsPath("kmp"), + icon: , + }, { title: "Unity (Beta)", description: "Integrate Superwall into your Unity mobile game.", href: buildDocsPath("unity"), icon: , }, + { + title: "Web (Beta)", + description: "Integrate Superwall into your web app with JavaScript or React.", + href: buildDocsPath("web"), + icon: , + }, { title: "React Native (Legacy)", description: "Legacy SDK for React Native. Migrate to the Expo SDK for new projects.", diff --git a/src/routes/kmp/llms-full[.]txt.ts b/src/routes/kmp/llms-full[.]txt.ts new file mode 100644 index 00000000..41bfafb1 --- /dev/null +++ b/src/routes/kmp/llms-full[.]txt.ts @@ -0,0 +1,10 @@ +import { buildLLMFullResponseForSection } from "@/lib/llms"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/kmp/llms-full.txt")({ + server: { + handlers: { + GET: () => buildLLMFullResponseForSection("kmp"), + }, + }, +}); diff --git a/src/routes/kmp/llms[.]txt.ts b/src/routes/kmp/llms[.]txt.ts new file mode 100644 index 00000000..580069ab --- /dev/null +++ b/src/routes/kmp/llms[.]txt.ts @@ -0,0 +1,10 @@ +import { buildLLMSummaryResponseForSection } from "@/lib/llms"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/kmp/llms.txt")({ + server: { + handlers: { + GET: () => buildLLMSummaryResponseForSection("kmp"), + }, + }, +}); diff --git a/src/routes/web/llms-full[.]txt.ts b/src/routes/web/llms-full[.]txt.ts new file mode 100644 index 00000000..06634e0f --- /dev/null +++ b/src/routes/web/llms-full[.]txt.ts @@ -0,0 +1,10 @@ +import { buildLLMFullResponseForSection } from "@/lib/llms"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/web/llms-full.txt")({ + server: { + handlers: { + GET: () => buildLLMFullResponseForSection("web"), + }, + }, +}); diff --git a/src/routes/web/llms[.]txt.ts b/src/routes/web/llms[.]txt.ts new file mode 100644 index 00000000..e5c2cf0d --- /dev/null +++ b/src/routes/web/llms[.]txt.ts @@ -0,0 +1,10 @@ +import { buildLLMSummaryResponseForSection } from "@/lib/llms"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/web/llms.txt")({ + server: { + handlers: { + GET: () => buildLLMSummaryResponseForSection("web"), + }, + }, +}); From 52a69ddd6b100ee05f39e512e25146dc341dfcf7 Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 19 Aug 2026 16:59:37 -0700 Subject: [PATCH 2/2] docs(vibe-coding): add the guide to KMP, Unity, and Web The shared LLMs.txt table only listed the sections that existed when it was written, so add the Agents, KMP, Unity, and Web rows now that every one of them has llms.txt endpoints. Also ship the guide itself in the three SDK sections that were missing it, using the same shared include as iOS, Android, Flutter, and Expo. --- content/docs/kmp/guides/vibe-coding.mdx | 6 ++++++ content/docs/kmp/meta.json | 1 + content/docs/unity/guides/vibe-coding.mdx | 6 ++++++ content/docs/unity/meta.json | 1 + content/docs/web/guides/vibe-coding.mdx | 6 ++++++ content/docs/web/meta.json | 1 + content/shared/vibe-coding.mdx | 4 ++++ 7 files changed, 25 insertions(+) create mode 100644 content/docs/kmp/guides/vibe-coding.mdx create mode 100644 content/docs/unity/guides/vibe-coding.mdx create mode 100644 content/docs/web/guides/vibe-coding.mdx diff --git a/content/docs/kmp/guides/vibe-coding.mdx b/content/docs/kmp/guides/vibe-coding.mdx new file mode 100644 index 00000000..b34e4c31 --- /dev/null +++ b/content/docs/kmp/guides/vibe-coding.mdx @@ -0,0 +1,6 @@ +--- +title: "Vibe Coding" +description: "How to Vibe Code using the knowledge of the Superwall Docs" +--- + +../../../shared/vibe-coding.mdx \ No newline at end of file diff --git a/content/docs/kmp/meta.json b/content/docs/kmp/meta.json index 42b7ba93..07ea704b 100644 --- a/content/docs/kmp/meta.json +++ b/content/docs/kmp/meta.json @@ -21,6 +21,7 @@ "guides/using-superwall-delegate", "guides/3rd-party-analytics", "guides/handling-deep-links", + "guides/vibe-coding", "[Example App](https://github.com/superwall/Superwall-KMP/tree/main/sample)" ] } diff --git a/content/docs/unity/guides/vibe-coding.mdx b/content/docs/unity/guides/vibe-coding.mdx new file mode 100644 index 00000000..b34e4c31 --- /dev/null +++ b/content/docs/unity/guides/vibe-coding.mdx @@ -0,0 +1,6 @@ +--- +title: "Vibe Coding" +description: "How to Vibe Code using the knowledge of the Superwall Docs" +--- + +../../../shared/vibe-coding.mdx \ No newline at end of file diff --git a/content/docs/unity/meta.json b/content/docs/unity/meta.json index 5b50efb9..b15a8a43 100644 --- a/content/docs/unity/meta.json +++ b/content/docs/unity/meta.json @@ -19,6 +19,7 @@ "guides/using-superwall-delegate", "guides/custom-purchase-controller", "guides/advanced-configuration", + "guides/vibe-coding", "---SDK Reference---", "sdk-reference/index", diff --git a/content/docs/web/guides/vibe-coding.mdx b/content/docs/web/guides/vibe-coding.mdx new file mode 100644 index 00000000..b34e4c31 --- /dev/null +++ b/content/docs/web/guides/vibe-coding.mdx @@ -0,0 +1,6 @@ +--- +title: "Vibe Coding" +description: "How to Vibe Code using the knowledge of the Superwall Docs" +--- + +../../../shared/vibe-coding.mdx \ No newline at end of file diff --git a/content/docs/web/meta.json b/content/docs/web/meta.json index ffc3f161..5570d2d2 100644 --- a/content/docs/web/meta.json +++ b/content/docs/web/meta.json @@ -19,6 +19,7 @@ "guides/server-side-gating", "guides/purchases", "guides/events", + "guides/vibe-coding", "[Example Apps](https://github.com/superwall/Superwall-Web/tree/main/example)" ] } diff --git a/content/shared/vibe-coding.mdx b/content/shared/vibe-coding.mdx index 61faebd7..1781a837 100644 --- a/content/shared/vibe-coding.mdx +++ b/content/shared/vibe-coding.mdx @@ -87,10 +87,14 @@ The Superwall Docs website has `llms.txt` and `llms-full.txt` files, in total an | ------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | All | [`llms.txt`](https://superwall.com/docs/llms.txt) | [`llms-full.txt`](https://superwall.com/docs/llms-full.txt) | | Dashboard | [`dashboard/llms.txt`](https://superwall.com/docs/dashboard/llms.txt) | [`dashboard/llms-full.txt`](https://superwall.com/docs/dashboard/llms-full.txt) | +| Agents | [`agents/llms.txt`](https://superwall.com/docs/agents/llms.txt) | [`agents/llms-full.txt`](https://superwall.com/docs/agents/llms-full.txt) | | iOS | [`ios/llms.txt`](https://superwall.com/docs/ios/llms.txt) | [`ios/llms-full.txt`](https://superwall.com/docs/ios/llms-full.txt) | | Android | [`android/llms.txt`](https://superwall.com/docs/android/llms.txt) | [`android/llms-full.txt`](https://superwall.com/docs/android/llms-full.txt) | | Flutter | [`flutter/llms.txt`](https://superwall.com/docs/flutter/llms.txt) | [`flutter/llms-full.txt`](https://superwall.com/docs/flutter/llms-full.txt) | | Expo | [`expo/llms.txt`](https://superwall.com/docs/expo/llms.txt) | [`expo/llms-full.txt`](https://superwall.com/docs/expo/llms-full.txt) | +| KMP (Beta) | [`kmp/llms.txt`](https://superwall.com/docs/kmp/llms.txt) | [`kmp/llms-full.txt`](https://superwall.com/docs/kmp/llms-full.txt) | +| Unity (Beta) | [`unity/llms.txt`](https://superwall.com/docs/unity/llms.txt) | [`unity/llms-full.txt`](https://superwall.com/docs/unity/llms-full.txt) | +| Web (Beta) | [`web/llms.txt`](https://superwall.com/docs/web/llms.txt) | [`web/llms-full.txt`](https://superwall.com/docs/web/llms-full.txt) | | React Native (Deprecated) | [`react-native/llms.txt`](https://superwall.com/docs/react-native/llms.txt) | [`react-native/llms-full.txt`](https://superwall.com/docs/react-native/llms-full.txt) | | Integrations | [`integrations/llms.txt`](https://superwall.com/docs/integrations/llms.txt) | [`integrations/llms-full.txt`](https://superwall.com/docs/integrations/llms-full.txt) | | Web Checkout | [`web-checkout/llms.txt`](https://superwall.com/docs/web-checkout/llms.txt) | [`web-checkout/llms-full.txt`](https://superwall.com/docs/web-checkout/llms-full.txt) |