doccupine 0.0.107 → 0.0.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
  2. package/dist/templates/components/layout/DocsComponents.js +2 -1
  3. package/dist/templates/mdx/ai-assistant.mdx.d.ts +1 -1
  4. package/dist/templates/mdx/ai-assistant.mdx.js +8 -0
  5. package/dist/templates/mdx/analytics.mdx.d.ts +1 -1
  6. package/dist/templates/mdx/analytics.mdx.js +1 -1
  7. package/dist/templates/mdx/components.mdx.d.ts +1 -1
  8. package/dist/templates/mdx/components.mdx.js +1 -1
  9. package/dist/templates/mdx/deployment-and-hosting.mdx.d.ts +1 -1
  10. package/dist/templates/mdx/deployment-and-hosting.mdx.js +2 -2
  11. package/dist/templates/mdx/fonts.mdx.d.ts +1 -1
  12. package/dist/templates/mdx/fonts.mdx.js +1 -1
  13. package/dist/templates/mdx/footer-links.mdx.d.ts +1 -1
  14. package/dist/templates/mdx/footer-links.mdx.js +1 -1
  15. package/dist/templates/mdx/globals.mdx.d.ts +1 -1
  16. package/dist/templates/mdx/globals.mdx.js +1 -1
  17. package/dist/templates/mdx/index.mdx.d.ts +1 -1
  18. package/dist/templates/mdx/index.mdx.js +1 -1
  19. package/dist/templates/mdx/media-and-assets.mdx.d.ts +1 -1
  20. package/dist/templates/mdx/media-and-assets.mdx.js +4 -4
  21. package/dist/templates/mdx/navigation.mdx.d.ts +1 -1
  22. package/dist/templates/mdx/navigation.mdx.js +1 -1
  23. package/dist/templates/mdx/platform/file-editor.mdx.d.ts +1 -1
  24. package/dist/templates/mdx/platform/file-editor.mdx.js +2 -2
  25. package/dist/templates/mdx/sections.mdx.d.ts +1 -1
  26. package/dist/templates/mdx/sections.mdx.js +1 -1
  27. package/dist/templates/mdx/theme.mdx.d.ts +1 -1
  28. package/dist/templates/mdx/theme.mdx.js +1 -1
  29. package/dist/templates/package.js +1 -1
  30. package/dist/templates/services/search.d.ts +1 -1
  31. package/dist/templates/services/search.js +39 -4
  32. package/package.json +1 -1
@@ -1 +1 @@
1
- export declare const docsComponentsTemplate = "\"use client\";\nimport React, { createContext, useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n resetButton,\n styledSmall,\n styledStrong,\n styledText,\n} from \"cherry-styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport {\n styledAnchor,\n styledTable,\n stylesLists,\n} from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst SectionBarContext = createContext(false);\n\nfunction SectionBarProvider({\n hasSectionBar,\n children,\n}: {\n hasSectionBar: boolean;\n children: React.ReactNode;\n}) {\n return (\n <SectionBarContext.Provider value={hasSectionBar}>\n {children}\n </SectionBarContext.Provider>\n );\n}\n\ninterface DocsProps {\n children: React.ReactNode;\n}\n\nconst StyledDocsWrapper = styled.main<{ theme: Theme }>`\n position: relative;\n`;\n\nconst StyledDocsSidebar = styled.div<{ theme: Theme }>`\n clear: both;\n`;\n\nconst StyledDocsContainer = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: relative;\n padding: 0 20px 100px 20px;\n width: 100%;\n ${({ theme }) => styledText(theme)};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 0 300px 80px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 300px;\n `}\n }\n\n & p {\n color: ${({ theme }) => theme.colors.grayDark};\n hyphens: auto;\n }\n\n & pre {\n max-width: 100%;\n }\n\n ${styledAnchor};\n ${stylesLists};\n ${styledTable};\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n\n & code:not([class]) {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 20%, transparent)`};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n white-space: pre;\n }\n\n & .lucide {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n & .aspect-video {\n aspect-ratio: 16 / 9;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n`;\n\nexport const StyledMarkdownContainer = styled.div`\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n max-width: 640px;\n margin: auto;\n`;\n\ninterface Props {\n theme?: Theme;\n $isActive?: boolean;\n $isOpen?: boolean;\n $hasSectionBar?: boolean;\n}\n\nexport const StyledSidebar = styled.nav<Props>`\n position: fixed;\n overflow-y: auto;\n max-height: calc(\n 100dvh - ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px\n );\n width: 100%;\n z-index: 99;\n top: ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px;\n height: 100%;\n padding: 20px;\n opacity: 0;\n pointer-events: none;\n transition: all 0.3s ease;\n transform: translateY(30px);\n left: 0;\n background: ${({ theme }) => theme.colors.light};\n -webkit-overflow-scrolling: touch;\n display: flex;\n flex-direction: column;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: none;\n max-height: 100dvh;\n width: 220px;\n background: transparent;\n padding: 82px 20px 20px 20px;\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n top: 0;\n width: 280px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n transform: translateY(0);\n opacity: 1;\n pointer-events: all;\n `}\n`;\n\nexport const StyledSidebarFooter = styled.div`\n padding: 22px 20px;\n position: sticky;\n border-top: 1px solid ${({ theme }) => theme.colors.grayLight};\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n margin: 0 -20px -20px;\n bottom: -20px;\n backdrop-filter: blur(10px);\n\n ${mq(\"lg\")} {\n padding: 16px 20px;\n }\n`;\n\nexport const StyledIndexSidebar = styled.ul<{ theme: Theme }>`\n display: none;\n list-style: none;\n margin: 0;\n padding: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 280px;\n height: 100dvh;\n overflow-y: auto;\n z-index: 1;\n padding: 82px 20px 20px 20px;\n background: ${({ theme }) => theme.colors.light};\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n -webkit-overflow-scrolling: touch;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n display: block;\n }\n\n & li {\n padding: 5px 0;\n }\n`;\n\nexport const StyledIndexSidebarLabel = styled.span<{ theme: Theme }>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nexport const StyledIndexSidebarLi = styled.li<{\n theme: Theme;\n $isActive: boolean;\n}>`\n &::before {\n content: \"\";\n display: block;\n position: absolute;\n left: 0;\n height: 20px;\n width: 1px;\n background: transparent;\n transition: all 0.3s ease;\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n &::before {\n background: ${theme.colors.primary};\n }\n `}\n`;\n\nexport const StyledIndexSidebarLink = styled.a<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.dark};\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"400\")};\n text-decoration: none;\n transition: all 0.3s ease;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nexport const StyledSidebarList = styled.ul`\n list-style: none;\n margin: 0;\n padding: 0;\n\n &:last-of-type {\n margin-bottom: auto;\n }\n`;\n\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) => theme.colors.accentStrong};\n display: inline-flex;\n align-items: center;\n gap: 8px;\n\n & svg {\n flex-shrink: 0;\n }\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\n// Shared appearance for every sidebar row - leaf links AND nested group\n// headers - so hover, active state, and the left rail read identically.\nconst sidebarRowStyles = css<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) => theme.colors.accentMuted};\n padding: 5px 0 5px 20px;\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n & svg {\n flex-shrink: 0;\n }\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n color: ${theme.colors.accentStrong};\n border-color: ${theme.colors.primary};\n font-weight: 600;\n `};\n`;\n\n// The collapse chevron points right when closed and rotates to point down\n// when the group is open.\nconst sidebarChevron = css<Props>`\n & .lucide-chevron-right {\n margin-left: auto;\n transition: transform 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n transform: rotate(90deg);\n `}\n }\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n ${sidebarRowStyles};\n`;\n\n// Nested navigation group. The header shares the link appearance/hover; a\n// non-navigable group is a single toggle button, while a group that is also a\n// page pairs a link with a chevron toggle. Children live in\n// StyledSidebarGroupContent, which animates open/closed via height 0 <-> auto\n// (enabled by interpolate-size in GlobalStyles, same as the Accordion).\nexport const StyledSidebarGroupButton = styled.button<Props>`\n ${resetButton};\n ${sidebarRowStyles};\n ${sidebarChevron};\n width: 100%;\n text-align: left;\n\n /* Buttons aren't covered by the global a:focus-visible ring, so this\n nav-row toggle carries its own copy of it. */\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const StyledSidebarGroupRow = styled.div<Props>`\n ${sidebarRowStyles};\n ${sidebarChevron};\n\n /* The row is a plain div, so it never matches :focus-visible itself. Ring the\n whole row when its inner link or chevron takes keyboard focus instead, to\n match the leaf-row focus treatment. */\n &:has(:focus-visible) {\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const StyledSidebarGroupLink = styled(Link)`\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n\n & svg {\n flex-shrink: 0;\n }\n\n /* Ring is drawn on the parent row via :has(:focus-visible); suppress the\n global a:focus-visible glow so it isn't drawn twice. */\n &:focus-visible {\n outline: none;\n box-shadow: none;\n }\n`;\n\nexport const StyledSidebarGroupChevron = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n cursor: pointer;\n\n /* Ring is drawn on the parent row via :has(:focus-visible). */\n &:focus-visible {\n outline: none;\n }\n`;\n\nexport const StyledSidebarGroupContent = styled.ul<Props>`\n list-style: none;\n margin: 0;\n padding: 0;\n height: 0;\n overflow: clip;\n transition: all 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n height: auto;\n `}\n`;\n\nexport const StyleMobileBar = styled.button<Props>`\n ${resetButton};\n position: fixed;\n z-index: 999;\n bottom: 0;\n right: 20px;\n font-size: ${({ theme }) => theme.fontSizes.strong.lg};\n line-height: ${({ theme }) => theme.fontSizes.strong.lg};\n box-shadow: ${({ theme }) => theme.shadows.sm};\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) => theme.colors.surface};\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n padding: 10px;\n border-radius: 100px;\n margin: 0 0 20px 0;\n font-weight: 600;\n display: flex;\n justify-content: flex-start;\n width: auto;\n\n ${mq(\"lg\")} {\n display: none;\n }\n\n ${({ $isActive }) => $isActive && `position: fixed;`};\n`;\n\nexport const StyledMobileBurger = styled.span<Props>`\n display: block;\n margin: auto 0;\n width: 18px;\n height: 18px;\n position: relative;\n overflow: hidden;\n background: transparent;\n position: relative;\n transform: scale(0.8);\n\n &::before,\n &::after {\n content: \"\";\n display: block;\n position: absolute;\n width: 18px;\n height: 3px;\n border-radius: 3px;\n background: ${({ theme }) => theme.colors.surface};\n transition: all 0.3s ease;\n }\n\n &::before {\n top: 3px;\n }\n\n &::after {\n bottom: 3px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n transform: translateY(5px) rotate(45deg);\n }\n\n &::after {\n transform: translateY(-4px) rotate(-45deg);\n }\n `};\n`;\n\nexport const StyledMissingComponent = styled.div`\n background: ${({ theme }) => theme.colors.error};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n color: ${({ theme }) => theme.colors.surface};\n font-weight: 600;\n display: flex;\n gap: 10px;\n align-items: center;\n`;\n\ninterface DocsWrapperProps {\n children: React.ReactNode;\n}\n\nfunction DocsWrapper({ children }: DocsWrapperProps) {\n return <StyledDocsWrapper>{children}</StyledDocsWrapper>;\n}\n\nfunction DocsSidebar({ children }: DocsProps) {\n return <StyledDocsSidebar>{children}</StyledDocsSidebar>;\n}\n\nfunction DocsContainer({ children }: DocsProps) {\n const { isOpen } = useContext(ChatContext);\n\n return (\n <StyledDocsContainer $isChatOpen={isOpen}>{children}</StyledDocsContainer>\n );\n}\n\nexport {\n DocsWrapper,\n DocsSidebar,\n DocsContainer,\n SectionBarContext,\n SectionBarProvider,\n};\n";
1
+ export declare const docsComponentsTemplate = "\"use client\";\nimport React, { createContext, useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport {\n resetButton,\n styledSmall,\n styledStrong,\n styledText,\n} from \"cherry-styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport {\n styledAnchor,\n styledTable,\n stylesLists,\n} from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst SectionBarContext = createContext(false);\n\nfunction SectionBarProvider({\n hasSectionBar,\n children,\n}: {\n hasSectionBar: boolean;\n children: React.ReactNode;\n}) {\n return (\n <SectionBarContext.Provider value={hasSectionBar}>\n {children}\n </SectionBarContext.Provider>\n );\n}\n\ninterface DocsProps {\n children: React.ReactNode;\n}\n\nconst StyledDocsWrapper = styled.main<{ theme: Theme }>`\n position: relative;\n`;\n\nconst StyledDocsSidebar = styled.div<{ theme: Theme }>`\n clear: both;\n`;\n\nconst StyledDocsContainer = styled.div<{ theme: Theme; $isChatOpen?: boolean }>`\n position: relative;\n padding: 0 20px 100px 20px;\n width: 100%;\n ${({ theme }) => styledText(theme)};\n transition: all 0.3s ease;\n\n ${mq(\"lg\")} {\n padding: 0 300px 80px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 300px;\n `}\n }\n\n & p {\n color: ${({ theme }) => theme.colors.grayDark};\n hyphens: auto;\n }\n\n & pre {\n max-width: 100%;\n }\n\n ${styledAnchor};\n ${stylesLists};\n ${styledTable};\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n\n & code:not([class]),\n & kbd {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 20%, transparent)`};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n white-space: pre;\n }\n\n & .lucide {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n & .aspect-video {\n aspect-ratio: 16 / 9;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n }\n`;\n\nexport const StyledMarkdownContainer = styled.div`\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n max-width: 640px;\n margin: auto;\n`;\n\ninterface Props {\n theme?: Theme;\n $isActive?: boolean;\n $isOpen?: boolean;\n $hasSectionBar?: boolean;\n}\n\nexport const StyledSidebar = styled.nav<Props>`\n position: fixed;\n overflow-y: auto;\n max-height: calc(\n 100dvh - ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px\n );\n width: 100%;\n z-index: 99;\n top: ${({ $hasSectionBar }) => ($hasSectionBar ? 104 : 62)}px;\n height: 100%;\n padding: 20px;\n opacity: 0;\n pointer-events: none;\n transition: all 0.3s ease;\n transform: translateY(30px);\n left: 0;\n background: ${({ theme }) => theme.colors.light};\n -webkit-overflow-scrolling: touch;\n display: flex;\n flex-direction: column;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n border-right: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: none;\n max-height: 100dvh;\n width: 220px;\n background: transparent;\n padding: 82px 20px 20px 20px;\n opacity: 1;\n pointer-events: all;\n transform: translateY(0);\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n top: 0;\n width: 280px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n transform: translateY(0);\n opacity: 1;\n pointer-events: all;\n `}\n`;\n\nexport const StyledSidebarFooter = styled.div`\n padding: 22px 20px;\n position: sticky;\n border-top: 1px solid ${({ theme }) => theme.colors.grayLight};\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n margin: 0 -20px -20px;\n bottom: -20px;\n backdrop-filter: blur(10px);\n\n ${mq(\"lg\")} {\n padding: 16px 20px;\n }\n`;\n\nexport const StyledIndexSidebar = styled.ul<{ theme: Theme }>`\n display: none;\n list-style: none;\n margin: 0;\n padding: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 280px;\n height: 100dvh;\n overflow-y: auto;\n z-index: 1;\n padding: 82px 20px 20px 20px;\n background: ${({ theme }) => theme.colors.light};\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n -webkit-overflow-scrolling: touch;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n ${mq(\"lg\")} {\n display: block;\n }\n\n & li {\n padding: 5px 0;\n }\n`;\n\nexport const StyledIndexSidebarLabel = styled.span<{ theme: Theme }>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n`;\n\nexport const StyledIndexSidebarLi = styled.li<{\n theme: Theme;\n $isActive: boolean;\n}>`\n &::before {\n content: \"\";\n display: block;\n position: absolute;\n left: 0;\n height: 20px;\n width: 1px;\n background: transparent;\n transition: all 0.3s ease;\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n &::before {\n background: ${theme.colors.primary};\n }\n `}\n`;\n\nexport const StyledIndexSidebarLink = styled.a<{\n theme: Theme;\n $isActive: boolean;\n}>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme, $isActive }) =>\n $isActive ? theme.colors.primary : theme.colors.dark};\n font-weight: ${({ $isActive }) => ($isActive ? \"600\" : \"400\")};\n text-decoration: none;\n transition: all 0.3s ease;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n`;\n\nexport const StyledSidebarList = styled.ul`\n list-style: none;\n margin: 0;\n padding: 0;\n\n &:last-of-type {\n margin-bottom: auto;\n }\n`;\n\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) => theme.colors.accentStrong};\n display: inline-flex;\n align-items: center;\n gap: 8px;\n\n & svg {\n flex-shrink: 0;\n }\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\n// Shared appearance for every sidebar row - leaf links AND nested group\n// headers - so hover, active state, and the left rail read identically.\nconst sidebarRowStyles = css<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) => theme.colors.accentMuted};\n padding: 5px 0 5px 20px;\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n & svg {\n flex-shrink: 0;\n }\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n css`\n color: ${theme.colors.accentStrong};\n border-color: ${theme.colors.primary};\n font-weight: 600;\n `};\n`;\n\n// The collapse chevron points right when closed and rotates to point down\n// when the group is open.\nconst sidebarChevron = css<Props>`\n & .lucide-chevron-right {\n margin-left: auto;\n transition: transform 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n transform: rotate(90deg);\n `}\n }\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n ${sidebarRowStyles};\n`;\n\n// Nested navigation group. The header shares the link appearance/hover; a\n// non-navigable group is a single toggle button, while a group that is also a\n// page pairs a link with a chevron toggle. Children live in\n// StyledSidebarGroupContent, which animates open/closed via height 0 <-> auto\n// (enabled by interpolate-size in GlobalStyles, same as the Accordion).\nexport const StyledSidebarGroupButton = styled.button<Props>`\n ${resetButton};\n ${sidebarRowStyles};\n ${sidebarChevron};\n width: 100%;\n text-align: left;\n\n /* Buttons aren't covered by the global a:focus-visible ring, so this\n nav-row toggle carries its own copy of it. */\n &:focus-visible {\n outline: none;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const StyledSidebarGroupRow = styled.div<Props>`\n ${sidebarRowStyles};\n ${sidebarChevron};\n\n /* The row is a plain div, so it never matches :focus-visible itself. Ring the\n whole row when its inner link or chevron takes keyboard focus instead, to\n match the leaf-row focus treatment. */\n &:has(:focus-visible) {\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const StyledSidebarGroupLink = styled(Link)`\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n\n & svg {\n flex-shrink: 0;\n }\n\n /* Ring is drawn on the parent row via :has(:focus-visible); suppress the\n global a:focus-visible glow so it isn't drawn twice. */\n &:focus-visible {\n outline: none;\n box-shadow: none;\n }\n`;\n\nexport const StyledSidebarGroupChevron = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n cursor: pointer;\n\n /* Ring is drawn on the parent row via :has(:focus-visible). */\n &:focus-visible {\n outline: none;\n }\n`;\n\nexport const StyledSidebarGroupContent = styled.ul<Props>`\n list-style: none;\n margin: 0;\n padding: 0;\n height: 0;\n overflow: clip;\n transition: all 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n height: auto;\n `}\n`;\n\nexport const StyleMobileBar = styled.button<Props>`\n ${resetButton};\n position: fixed;\n z-index: 999;\n bottom: 0;\n right: 20px;\n font-size: ${({ theme }) => theme.fontSizes.strong.lg};\n line-height: ${({ theme }) => theme.fontSizes.strong.lg};\n box-shadow: ${({ theme }) => theme.shadows.sm};\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) => theme.colors.surface};\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n padding: 10px;\n border-radius: 100px;\n margin: 0 0 20px 0;\n font-weight: 600;\n display: flex;\n justify-content: flex-start;\n width: auto;\n\n ${mq(\"lg\")} {\n display: none;\n }\n\n ${({ $isActive }) => $isActive && `position: fixed;`};\n`;\n\nexport const StyledMobileBurger = styled.span<Props>`\n display: block;\n margin: auto 0;\n width: 18px;\n height: 18px;\n position: relative;\n overflow: hidden;\n background: transparent;\n position: relative;\n transform: scale(0.8);\n\n &::before,\n &::after {\n content: \"\";\n display: block;\n position: absolute;\n width: 18px;\n height: 3px;\n border-radius: 3px;\n background: ${({ theme }) => theme.colors.surface};\n transition: all 0.3s ease;\n }\n\n &::before {\n top: 3px;\n }\n\n &::after {\n bottom: 3px;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n transform: translateY(5px) rotate(45deg);\n }\n\n &::after {\n transform: translateY(-4px) rotate(-45deg);\n }\n `};\n`;\n\nexport const StyledMissingComponent = styled.div`\n background: ${({ theme }) => theme.colors.error};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n color: ${({ theme }) => theme.colors.surface};\n font-weight: 600;\n display: flex;\n gap: 10px;\n align-items: center;\n`;\n\ninterface DocsWrapperProps {\n children: React.ReactNode;\n}\n\nfunction DocsWrapper({ children }: DocsWrapperProps) {\n return <StyledDocsWrapper>{children}</StyledDocsWrapper>;\n}\n\nfunction DocsSidebar({ children }: DocsProps) {\n return <StyledDocsSidebar>{children}</StyledDocsSidebar>;\n}\n\nfunction DocsContainer({ children }: DocsProps) {\n const { isOpen } = useContext(ChatContext);\n\n return (\n <StyledDocsContainer $isChatOpen={isOpen}>{children}</StyledDocsContainer>\n );\n}\n\nexport {\n DocsWrapper,\n DocsSidebar,\n DocsContainer,\n SectionBarContext,\n SectionBarProvider,\n};\n";
@@ -82,7 +82,8 @@ const StyledDocsContainer = styled.div<{ theme: Theme; $isChatOpen?: boolean }>\
82
82
  border-radius: \${({ theme }) => theme.spacing.radius.lg};
83
83
  }
84
84
 
85
- & code:not([class]) {
85
+ & code:not([class]),
86
+ & kbd {
86
87
  background: \${({ theme }) =>
87
88
  \`color-mix(in srgb, \${theme.colors.primaryLight} 20%, transparent)\`};
88
89
  color: \${({ theme }) => theme.colors.dark};
@@ -1 +1 @@
1
- export declare const aiAssistantMdxTemplate = "---\ntitle: \"AI Assistant\"\ndescription: \"Integrate AI capabilities into your Doccupine documentation using OpenAI, Anthropic, or Google Gemini.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 8\n---\n\n# AI Assistant\n\nDoccupine supports AI integration to enhance your documentation experience. You can use OpenAI, Anthropic, or Google Gemini to power AI features in your documentation site. The AI assistant uses your documentation content as context, allowing users to ask questions about your docs and receive accurate answers based on the documentation.\n\n## Setup\n\nTo enable AI features, create an `.env` file in the directory where your website is generated. By default, this is the `nextjs-app/` directory.\n\n## Configuration\n\nCreate an `.env` file with the following configuration options:\n\n```env\n# LLM Provider Configuration\n# Choose your preferred LLM provider: openai, anthropic, or google\nLLM_PROVIDER=openai\n\n# API Keys (set the one matching your provider)\nOPENAI_API_KEY=your_openai_api_key_here\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nGOOGLE_API_KEY=your_google_api_key_here\n\n# Optional: Override default chat model (see your provider's docs for available models)\n# LLM_CHAT_MODEL=your-model-id\n\n# Optional: Override default embedding model (see your provider's docs for available models)\n# Note: Anthropic doesn't provide embeddings, will fallback to OpenAI\n# LLM_EMBEDDING_MODEL=your-embedding-model-id\n\n# Optional: Set temperature (0-1, default: 0)\n# LLM_TEMPERATURE=0\n```\n\n## Provider Selection\n\nSet `LLM_PROVIDER` to one of the following values:\n\n- `openai` - Use OpenAI's models\n- `anthropic` - Use Anthropic's models\n- `google` - Use Google's models\n\n## API Keys\n\nYou need to set the API key that matches your chosen provider:\n\n- For OpenAI: Set `OPENAI_API_KEY`\n- For Anthropic: Set `ANTHROPIC_API_KEY`\n- For Google: Set `GOOGLE_API_KEY`\n\n<Callout type=\"warning\">\n Keep your API keys secure. Never commit your `.env` file to version control.\n</Callout>\n\n<Callout type=\"note\">\n Doccupine automatically adds `.env` to your `.gitignore` file.\n</Callout>\n\n## Using Anthropic with OpenAI\n\nIf you want to use Anthropic as your LLM provider, you must also have an OpenAI API key set. Here's why:\n\n### The Situation\n\nAnthropic (Claude) does not provide an embeddings API. They only offer chat/completion models, not text embeddings.\n\nYour RAG (Retrieval-Augmented Generation) system has two components:\n\n- **Chat/Completion** - Generates answers, works with Anthropic.\n- **Embeddings** - Creates vector representations of text for search, Anthropic doesn't provide this.\n\nWhen using Anthropic as your `LLM_PROVIDER`, Doccupine will use Anthropic for chat/completion tasks, but will automatically fallback to OpenAI for embeddings. This means you need both API keys configured:\n\n```env\nLLM_PROVIDER=anthropic\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nOPENAI_API_KEY=your_openai_api_key_here\n```\n\nThis hybrid approach allows you to leverage Anthropic's powerful chat models while still having access to embeddings functionality through OpenAI.\n\n## Default models\n\n| Provider | Chat model | Embedding model |\n| --------- | ---------------------------- | ------------------------ |\n| OpenAI | `gpt-4.1-nano` | `text-embedding-3-small` |\n| Anthropic | `claude-sonnet-4-5-20250929` | OpenAI fallback |\n| Google | `gemini-2.5-flash-lite` | `gemini-embedding-001` |\n\n## Optional Settings\n\n### Chat Model\n\nOverride the default chat model by uncommenting and setting `LLM_CHAT_MODEL`. You can use any available model from your chosen provider. For a complete list of available models, refer to the official documentation:\n\n- [OpenAI Models](https://platform.openai.com/docs/models)\n- [Anthropic Models](https://docs.anthropic.com/claude/docs/models-overview)\n- [Google Gemini Models](https://ai.google.dev/models/gemini)\n\n### Embedding Model\n\nOverride the default embedding model by uncommenting and setting `LLM_EMBEDDING_MODEL`. For a complete list of available embedding models, refer to the official documentation:\n\n- [OpenAI Embeddings](https://platform.openai.com/docs/guides/embeddings)\n- [Google Gemini Embeddings](https://ai.google.dev/gemini-api/docs/embeddings)\n- **Anthropic**: Anthropic doesn't provide embeddings. If you use Anthropic as your provider, Doccupine will fallback to OpenAI for embeddings.\n\n### Temperature\n\nControl the randomness of AI responses by setting `LLM_TEMPERATURE` to a value between 0 and 1:\n\n- `0` - More deterministic and focused responses (default)\n- `1` - More creative and varied responses";
1
+ export declare const aiAssistantMdxTemplate = "---\ntitle: \"AI Assistant\"\ndescription: \"Integrate AI capabilities into your Doccupine documentation using OpenAI, Anthropic, or Google Gemini.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 8\n---\n\n# AI Assistant\n\nDoccupine supports AI integration to enhance your documentation experience. You can use OpenAI, Anthropic, or Google Gemini to power AI features in your documentation site. The AI assistant uses your documentation content as context, allowing users to ask questions about your docs and receive accurate answers based on the documentation.\n\n## Opening the Assistant\n\nOnce a provider is configured, an assistant button appears in the header. You can open and close the assistant from anywhere on the site by pressing <kbd>Command</kbd> + <kbd>I</kbd> (<kbd>Ctrl</kbd> + <kbd>I</kbd> on Windows). Press the same shortcut again to dismiss it.\n\n<Callout type=\"note\">\n The keyboard shortcut is only active once you have configured an LLM provider, as described below.\n</Callout>\n\n## Setup\n\nTo enable AI features, create an `.env` file in the directory where your website is generated. By default, this is the `nextjs-app/` directory.\n\n## Configuration\n\nCreate an `.env` file with the following configuration options:\n\n```env\n# LLM Provider Configuration\n# Choose your preferred LLM provider: openai, anthropic, or google\nLLM_PROVIDER=openai\n\n# API Keys (set the one matching your provider)\nOPENAI_API_KEY=your_openai_api_key_here\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nGOOGLE_API_KEY=your_google_api_key_here\n\n# Optional: Override default chat model (see your provider's docs for available models)\n# LLM_CHAT_MODEL=your-model-id\n\n# Optional: Override default embedding model (see your provider's docs for available models)\n# Note: Anthropic doesn't provide embeddings, will fallback to OpenAI\n# LLM_EMBEDDING_MODEL=your-embedding-model-id\n\n# Optional: Set temperature (0-1, default: 0)\n# LLM_TEMPERATURE=0\n```\n\n## Provider Selection\n\nSet `LLM_PROVIDER` to one of the following values:\n\n- `openai` - Use OpenAI's models\n- `anthropic` - Use Anthropic's models\n- `google` - Use Google's models\n\n## API Keys\n\nYou need to set the API key that matches your chosen provider:\n\n- For OpenAI: Set `OPENAI_API_KEY`\n- For Anthropic: Set `ANTHROPIC_API_KEY`\n- For Google: Set `GOOGLE_API_KEY`\n\n<Callout type=\"warning\">\n Keep your API keys secure. Never commit your `.env` file to version control.\n</Callout>\n\n<Callout type=\"note\">\n Doccupine automatically adds `.env` to your `.gitignore` file.\n</Callout>\n\n## Using Anthropic with OpenAI\n\nIf you want to use Anthropic as your LLM provider, you must also have an OpenAI API key set. Here's why:\n\n### The Situation\n\nAnthropic (Claude) does not provide an embeddings API. They only offer chat/completion models, not text embeddings.\n\nYour RAG (Retrieval-Augmented Generation) system has two components:\n\n- **Chat/Completion** - Generates answers, works with Anthropic.\n- **Embeddings** - Creates vector representations of text for search, Anthropic doesn't provide this.\n\nWhen using Anthropic as your `LLM_PROVIDER`, Doccupine will use Anthropic for chat/completion tasks, but will automatically fallback to OpenAI for embeddings. This means you need both API keys configured:\n\n```env\nLLM_PROVIDER=anthropic\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nOPENAI_API_KEY=your_openai_api_key_here\n```\n\nThis hybrid approach allows you to leverage Anthropic's powerful chat models while still having access to embeddings functionality through OpenAI.\n\n## Default models\n\n| Provider | Chat model | Embedding model |\n| --------- | ---------------------------- | ------------------------ |\n| OpenAI | `gpt-4.1-nano` | `text-embedding-3-small` |\n| Anthropic | `claude-sonnet-4-5-20250929` | OpenAI fallback |\n| Google | `gemini-2.5-flash-lite` | `gemini-embedding-001` |\n\n## Optional Settings\n\n### Chat Model\n\nOverride the default chat model by uncommenting and setting `LLM_CHAT_MODEL`. You can use any available model from your chosen provider. For a complete list of available models, refer to the official documentation:\n\n- [OpenAI Models](https://platform.openai.com/docs/models)\n- [Anthropic Models](https://docs.anthropic.com/claude/docs/models-overview)\n- [Google Gemini Models](https://ai.google.dev/models/gemini)\n\n### Embedding Model\n\nOverride the default embedding model by uncommenting and setting `LLM_EMBEDDING_MODEL`. For a complete list of available embedding models, refer to the official documentation:\n\n- [OpenAI Embeddings](https://platform.openai.com/docs/guides/embeddings)\n- [Google Gemini Embeddings](https://ai.google.dev/gemini-api/docs/embeddings)\n- **Anthropic**: Anthropic doesn't provide embeddings. If you use Anthropic as your provider, Doccupine will fallback to OpenAI for embeddings.\n\n### Temperature\n\nControl the randomness of AI responses by setting `LLM_TEMPERATURE` to a value between 0 and 1:\n\n- `0` - More deterministic and focused responses (default)\n- `1` - More creative and varied responses";
@@ -11,6 +11,14 @@ order: 8
11
11
 
12
12
  Doccupine supports AI integration to enhance your documentation experience. You can use OpenAI, Anthropic, or Google Gemini to power AI features in your documentation site. The AI assistant uses your documentation content as context, allowing users to ask questions about your docs and receive accurate answers based on the documentation.
13
13
 
14
+ ## Opening the Assistant
15
+
16
+ Once a provider is configured, an assistant button appears in the header. You can open and close the assistant from anywhere on the site by pressing <kbd>Command</kbd> + <kbd>I</kbd> (<kbd>Ctrl</kbd> + <kbd>I</kbd> on Windows). Press the same shortcut again to dismiss it.
17
+
18
+ <Callout type="note">
19
+ The keyboard shortcut is only active once you have configured an LLM provider, as described below.
20
+ </Callout>
21
+
14
22
  ## Setup
15
23
 
16
24
  To enable AI features, create an \`.env\` file in the directory where your website is generated. By default, this is the \`nextjs-app/\` directory.
@@ -1 +1 @@
1
- export declare const analyticsMdxTemplate = "---\ntitle: \"Analytics\"\ndescription: \"Add PostHog analytics to your documentation site with an analytics.json file for both client-side and server-side tracking.\"\ndate: \"2026-02-24\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 10\n---\n\n# Analytics\n\nTrack how users interact with your documentation using PostHog. Doccupine supports both client-side and server-side tracking out of the box, with a privacy-first proxy that routes analytics through your own domain.\n\n## analytics.json\n\nPlace an `analytics.json` at your project root (the same folder where you execute `npx doccupine`).\n\n```json\n{\n \"provider\": \"posthog\",\n \"posthog\": {\n \"key\": \"phc_your_project_api_key\",\n \"host\": \"https://us.i.posthog.com\"\n }\n}\n```\n\n## Fields\n\n- **provider**: The analytics provider to use. Currently only `\"posthog\"` is supported.\n- **posthog.key**: Your PostHog project API key. You can find this in your PostHog project settings under \"Project API Key\". This is a public identifier - it is safe to commit to version control.\n- **posthog.host**: The PostHog ingestion endpoint. Use `https://us.i.posthog.com` for US Cloud or `https://eu.i.posthog.com` for EU Cloud. If you self-host PostHog, use your instance URL.\n\n<Callout type=\"note\">\n The PostHog project API key is a public identifier used to send events. It is not a secret and is safe to include in your repository.\n</Callout>\n\n## What gets tracked\n\nWhen `analytics.json` is configured, Doccupine enables two layers of tracking:\n\n### Client-side\n\n- **Page views**: Captured on every client-side navigation using Next.js router hooks.\n- **Page leave**: Automatically captured when a user navigates away from a page.\n\n### Server-side\n\n- **Page views**: Captured in middleware on every page request, including the initial server render.\n- **Request metadata**: URL, pathname, host, referrer, and user agent are sent with each event.\n- **Smart filtering**: API routes, internal Next.js routes, and prefetch requests are automatically excluded.\n\n## Privacy proxy\n\nDoccupine routes all analytics traffic through your documentation domain using Next.js rewrites. Instead of sending data directly to PostHog (which ad blockers may intercept), requests go through `/ingest` on your own domain and are proxied to PostHog.\n\nThis means:\n\n- No third-party domains appear in network requests.\n- Ad blockers are less likely to interfere with tracking.\n- Your users' browsing data stays within your domain boundary before reaching PostHog.\n\nThe proxy destinations are derived automatically from the `host` field in your configuration.\n\n## Getting a PostHog key\n\n1. Sign up at [posthog.com](https://posthog.com) (free tier available).\n2. Create a new project.\n3. Go to **Project Settings** and copy the **Project API Key**.\n4. Paste it into your `analytics.json` as the `posthog.key` value.\n\n## Behavior\n\n- **Placement**: Put `analytics.json` in the project root alongside `config.json` and `theme.json`.\n- **Hot reload**: Changes to `analytics.json` are picked up automatically in watch mode. The layout, middleware, and Next.js config are regenerated.\n- **Graceful degradation**: If `analytics.json` is missing, empty, or has an invalid configuration, no tracking code runs. Your site works exactly the same without it.\n- **Dev server restart**: After adding or removing `analytics.json` for the first time, you may need to restart the Next.js dev server for proxy rewrites to take effect.\n\n<Callout type=\"warning\">\n After adding `analytics.json` for the first time, restart the dev server so the proxy rewrites are picked up by Next.js.\n</Callout>\n\n## Regions\n\nPostHog offers two cloud regions. Set the `host` field accordingly:\n\n| Region | Host |\n| -------- | -------------------------- |\n| US Cloud | `https://us.i.posthog.com` |\n| EU Cloud | `https://eu.i.posthog.com` |\n\nIf you omit the `host` field, it defaults to the US Cloud endpoint.\n\n## Example\n\n### Minimal configuration (US Cloud)\n\n```json\n{\n \"provider\": \"posthog\",\n \"posthog\": {\n \"key\": \"phc_your_project_api_key\"\n }\n}\n```\n\n### EU Cloud\n\n```json\n{\n \"provider\": \"posthog\",\n \"posthog\": {\n \"key\": \"phc_your_project_api_key\",\n \"host\": \"https://eu.i.posthog.com\"\n }\n}\n```\n\n## Tips\n\n- **Start simple**: Add the config with just your key and verify events appear in your PostHog dashboard before customizing further.\n- **Check your dashboard**: After deploying, visit your PostHog project to confirm page view events are flowing in.\n- **Production only**: Consider adding `analytics.json` only in your production/deployment setup to avoid tracking local development traffic.";
1
+ export declare const analyticsMdxTemplate = "---\ntitle: \"Analytics\"\ndescription: \"Add PostHog analytics to your documentation site with an analytics.json file for both client-side and server-side tracking.\"\ndate: \"2026-02-24\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 10\n---\n\n# Analytics\n\nTrack how users interact with your documentation using PostHog. Doccupine supports both client-side and server-side tracking out of the box, with a privacy-first proxy that routes analytics through your own domain.\n\n## analytics.json\n\nPlace an `analytics.json` at your project root (the same directory where you execute `npx doccupine`).\n\n```json\n{\n \"provider\": \"posthog\",\n \"posthog\": {\n \"key\": \"phc_your_project_api_key\",\n \"host\": \"https://us.i.posthog.com\"\n }\n}\n```\n\n## Fields\n\n- **provider**: The analytics provider to use. Currently only `\"posthog\"` is supported.\n- **posthog.key**: Your PostHog project API key. You can find this in your PostHog project settings under \"Project API Key\". This is a public identifier - it is safe to commit to version control.\n- **posthog.host**: The PostHog ingestion endpoint. Use `https://us.i.posthog.com` for US Cloud or `https://eu.i.posthog.com` for EU Cloud. If you self-host PostHog, use your instance URL.\n\n<Callout type=\"note\">\n The PostHog project API key is a public identifier used to send events. It is not a secret and is safe to include in your repository.\n</Callout>\n\n## What gets tracked\n\nWhen `analytics.json` is configured, Doccupine enables two layers of tracking:\n\n### Client-side\n\n- **Page views**: Captured on every client-side navigation using Next.js router hooks.\n- **Page leave**: Automatically captured when a user navigates away from a page.\n\n### Server-side\n\n- **Page views**: Captured in middleware on every page request, including the initial server render.\n- **Request metadata**: URL, pathname, host, referrer, and user agent are sent with each event.\n- **Smart filtering**: API routes, internal Next.js routes, and prefetch requests are automatically excluded.\n\n## Privacy proxy\n\nDoccupine routes all analytics traffic through your documentation domain using Next.js rewrites. Instead of sending data directly to PostHog (which ad blockers may intercept), requests go through `/ingest` on your own domain and are proxied to PostHog.\n\nThis means:\n\n- No third-party domains appear in network requests.\n- Ad blockers are less likely to interfere with tracking.\n- Your users' browsing data stays within your domain boundary before reaching PostHog.\n\nThe proxy destinations are derived automatically from the `host` field in your configuration.\n\n## Getting a PostHog key\n\n1. Sign up at [posthog.com](https://posthog.com) (free tier available).\n2. Create a new project.\n3. Go to **Project Settings** and copy the **Project API Key**.\n4. Paste it into your `analytics.json` as the `posthog.key` value.\n\n## Behavior\n\n- **Placement**: Put `analytics.json` in the project root alongside `config.json` and `theme.json`.\n- **Hot reload**: Changes to `analytics.json` are picked up automatically in watch mode. The layout, middleware, and Next.js config are regenerated.\n- **Graceful degradation**: If `analytics.json` is missing, empty, or has an invalid configuration, no tracking code runs. Your site works exactly the same without it.\n- **Dev server restart**: After adding or removing `analytics.json` for the first time, you may need to restart the Next.js dev server for proxy rewrites to take effect.\n\n<Callout type=\"warning\">\n After adding `analytics.json` for the first time, restart the dev server so the proxy rewrites are picked up by Next.js.\n</Callout>\n\n## Regions\n\nPostHog offers two cloud regions. Set the `host` field accordingly:\n\n| Region | Host |\n| -------- | -------------------------- |\n| US Cloud | `https://us.i.posthog.com` |\n| EU Cloud | `https://eu.i.posthog.com` |\n\nIf you omit the `host` field, it defaults to the US Cloud endpoint.\n\n## Example\n\n### Minimal configuration (US Cloud)\n\n```json\n{\n \"provider\": \"posthog\",\n \"posthog\": {\n \"key\": \"phc_your_project_api_key\"\n }\n}\n```\n\n### EU Cloud\n\n```json\n{\n \"provider\": \"posthog\",\n \"posthog\": {\n \"key\": \"phc_your_project_api_key\",\n \"host\": \"https://eu.i.posthog.com\"\n }\n}\n```\n\n## Tips\n\n- **Start simple**: Add the config with just your key and verify events appear in your PostHog dashboard before customizing further.\n- **Check your dashboard**: After deploying, visit your PostHog project to confirm page view events are flowing in.\n- **Production only**: Consider adding `analytics.json` only in your production/deployment setup to avoid tracking local development traffic.";
@@ -13,7 +13,7 @@ Track how users interact with your documentation using PostHog. Doccupine suppor
13
13
 
14
14
  ## analytics.json
15
15
 
16
- Place an \`analytics.json\` at your project root (the same folder where you execute \`npx doccupine\`).
16
+ Place an \`analytics.json\` at your project root (the same directory where you execute \`npx doccupine\`).
17
17
 
18
18
  \`\`\`json
19
19
  {
@@ -1 +1 @@
1
- export declare const componentsMdxTemplate = "---\ntitle: \"Components\"\ndescription: \"Explore the full library of built-in components available in your documentation pages.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\ncategoryIcon: \"blocks\"\norder: 0\n---\n\n# Components\n\nDoccupine includes a rich set of built-in components you can use directly in your MDX files - no imports needed. Browse the full library below.\n\n<Columns cols={3}>\n <Card title=\"Headers and Text\" icon=\"heading\" href=\"/headers-and-text\">\n Headings, paragraphs, bold, italic, links, and other text formatting.\n </Card>\n <Card title=\"Lists and Tables\" icon=\"list\" href=\"/lists-and-tables\">\n Ordered lists, unordered lists, and data tables.\n </Card>\n <Card title=\"Code\" icon=\"code\" href=\"/code\">\n Inline code, fenced code blocks, and syntax highlighting.\n </Card>\n <Card title=\"Accordion\" icon=\"chevrons-down\" href=\"/accordion\">\n Collapsible sections for organizing lengthy content.\n </Card>\n <Card title=\"Tabs\" icon=\"panel-top\" href=\"/tabs\">\n Tabbed interfaces for grouping related content.\n </Card>\n <Card title=\"Cards\" icon=\"square\" href=\"/cards\">\n Visual containers for content, icons, and links.\n </Card>\n <Card title=\"Buttons\" icon=\"mouse-pointer-click\" href=\"/buttons\">\n Action components with variants, sizes, and icons.\n </Card>\n <Card title=\"Callouts\" icon=\"megaphone\" href=\"/callouts\">\n Highlighted blocks for tips, warnings, and important notes.\n </Card>\n <Card title=\"Images and Embeds\" icon=\"image\" href=\"/images-and-embeds\">\n Images, videos, and embedded content.\n </Card>\n <Card title=\"Icons\" icon=\"smile\" href=\"/icons\">\n Lucide icons you can use anywhere in your docs.\n </Card>\n <Card title=\"Fields\" icon=\"text-cursor-input\" href=\"/fields\">\n Property and parameter documentation blocks.\n </Card>\n <Card title=\"Update\" icon=\"bell\" href=\"/update\">\n Changelog and update announcement blocks.\n </Card>\n <Card title=\"Columns\" icon=\"columns-3\" href=\"/columns\">\n Grid layouts for arranging cards and content side by side.\n </Card>\n <Card title=\"Steps\" icon=\"footprints\" href=\"/steps\">\n Numbered step-by-step guides and walkthroughs.\n </Card>\n <Card title=\"Color Swatches\" icon=\"palette\" href=\"/color-swatches\">\n Visual color palette swatches to document your theme colors.\n </Card>\n</Columns>";
1
+ export declare const componentsMdxTemplate = "---\ntitle: \"Components\"\ndescription: \"Explore the full library of built-in components available in your documentation pages.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\ncategoryIcon: \"blocks\"\norder: 0\n---\n\n# Components\n\nDoccupine includes a rich set of built-in components you can use directly in your MDX files - no imports needed. Browse the full library below.\n\n<Columns cols={3}>\n <Card title=\"Headers and Text\" icon=\"heading\" href=\"/headers-and-text\">\n Headings, paragraphs, bold, italic, links, and other text formatting.\n </Card>\n <Card title=\"Lists and Tables\" icon=\"list\" href=\"/lists-and-tables\">\n Ordered lists, unordered lists, and data tables.\n </Card>\n <Card title=\"Code\" icon=\"code\" href=\"/code\">\n Inline code, fenced code blocks, and syntax highlighting.\n </Card>\n <Card title=\"Accordion\" icon=\"chevrons-down\" href=\"/accordion\">\n Collapsible sections for organizing lengthy content.\n </Card>\n <Card title=\"Tabs\" icon=\"panel-top\" href=\"/tabs\">\n Tabbed interfaces for grouping related content.\n </Card>\n <Card title=\"Cards\" icon=\"square\" href=\"/cards\">\n Visual containers for content, icons, and links.\n </Card>\n <Card title=\"Buttons\" icon=\"mouse-pointer-click\" href=\"/buttons\">\n Action components with variants, sizes, and icons.\n </Card>\n <Card title=\"Callouts\" icon=\"megaphone\" href=\"/callouts\">\n Highlighted blocks for tips, warnings, and important notes.\n </Card>\n <Card title=\"Images and Embeds\" icon=\"image\" href=\"/image-and-embeds\">\n Images, videos, and embedded content.\n </Card>\n <Card title=\"Icons\" icon=\"smile\" href=\"/icons\">\n Lucide icons you can use anywhere in your docs.\n </Card>\n <Card title=\"Fields\" icon=\"text-cursor-input\" href=\"/fields\">\n Property and parameter documentation blocks.\n </Card>\n <Card title=\"Update\" icon=\"bell\" href=\"/update\">\n Changelog and update announcement blocks.\n </Card>\n <Card title=\"Columns\" icon=\"columns-3\" href=\"/columns\">\n Grid layouts for arranging cards and content side by side.\n </Card>\n <Card title=\"Steps\" icon=\"footprints\" href=\"/steps\">\n Numbered step-by-step guides and walkthroughs.\n </Card>\n <Card title=\"Color Swatches\" icon=\"palette\" href=\"/color-swatches\">\n Visual color palette swatches to document your theme colors.\n </Card>\n</Columns>";
@@ -37,7 +37,7 @@ Doccupine includes a rich set of built-in components you can use directly in you
37
37
  <Card title="Callouts" icon="megaphone" href="/callouts">
38
38
  Highlighted blocks for tips, warnings, and important notes.
39
39
  </Card>
40
- <Card title="Images and Embeds" icon="image" href="/images-and-embeds">
40
+ <Card title="Images and Embeds" icon="image" href="/image-and-embeds">
41
41
  Images, videos, and embedded content.
42
42
  </Card>
43
43
  <Card title="Icons" icon="smile" href="/icons">
@@ -1 +1 @@
1
- export declare const deploymentAndHostingMdxTemplate = "---\ntitle: \"Deployment & Hosting\"\ndescription: \"Deploy your documentation site with the Doccupine Platform or self-host on any platform that supports Next.js.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 11\n---\n\n# Deployment & Hosting\n\nThe fastest way to deploy your documentation is with the [Doccupine Platform](https://www.doccupine.com). If you prefer to manage your own infrastructure, you can self-host the generated Next.js app on any platform.\n\n## Doccupine Platform\n\nSign up at [doccupine.com](https://www.doccupine.com) and connect your repository. Your documentation site is live in minutes - no build configuration, no infrastructure to manage.\n\n<Callout type=\"success\">\n The Doccupine Platform is the recommended way to deploy. It handles builds, hosting, SSL, and updates automatically so you can focus on writing documentation.\n</Callout>\n\n### What you get\n\n- **Automatic deployments** on every push to your repository\n- **Site customization** through a visual dashboard - no code changes needed\n- **Team collaboration** so your whole team can manage docs together\n- **Custom domains** with automatic SSL\n- **AI Assistant and MCP server** included out of the box, no API key required\n- **Zero maintenance** - no servers, no build pipelines, no dependency updates\n\n### Getting started\n\n1. Create an account at [doccupine.com](https://www.doccupine.com).\n2. Connect your GitHub repository.\n3. Your site is deployed automatically.\n\nEvery push to your repository triggers a new deployment. You can customize your site's appearance, domain, and settings from the dashboard. See the [Platform Overview](/platform) for a full walkthrough of the dashboard, editor, and configuration options.\n\n---\n\n## Self-hosting\n\nDoccupine generates a standard Next.js app, so you can deploy it anywhere that supports Node.js or Next.js.\n\n<Callout type=\"warning\">\n Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the root directory to the generated site folder.\n</Callout>\n\n<Callout type=\"note\">\n Self-hosting requires you to manage your own build pipeline, hosting, SSL certificates, and AI provider API keys. For a hands-off experience, consider the [Doccupine Platform](https://www.doccupine.com).\n</Callout>\n\n### Popular hosting options\n\n- **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app folder.\n- **Netlify** - supports Next.js via the `@netlify/plugin-nextjs` adapter. Works with the standard `next build` output.\n- **AWS Amplify** - fully managed hosting with CI/CD. Supports Next.js SSR out of the box.\n- **Cloudflare Pages** - deploy using the `@cloudflare/next-on-pages` adapter for edge-based hosting.\n- **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.\n- **Node.js server** - run `next build && next start` on any server or VPS with Node.js installed.\n\n### Sitemap and robots.txt\n\nDoccupine generates `sitemap.xml` and `robots.txt` automatically when you set a site URL. This is required for search engine indexing and is strongly recommended for any public deployment.\n\nSet the URL in `config.json`:\n\n```json\n{\n \"url\": \"https://docs.example.com\"\n}\n```\n\nAt deploy time, you can override the value with the `NEXT_PUBLIC_SITE_URL` environment variable - useful for preview deployments or staging environments.\n\n```bash\nNEXT_PUBLIC_SITE_URL=https://staging.example.com\n```\n\nThe generated sitemap includes every page from every [section](/sections), with `lastModified` derived from each page's frontmatter `date` or the file's modification time. When no URL is configured, the sitemap is skipped and `robots.txt` is emitted without a sitemap reference.\n\n### Troubleshooting\n\n- **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.\n- **Missing content** - verify your MDX files and assets are in the repository.\n- **SSR issues on edge platforms** - some features (like the AI chat API routes) require a Node.js runtime. Check your platform's documentation for SSR/API route support.";
1
+ export declare const deploymentAndHostingMdxTemplate = "---\ntitle: \"Deployment & Hosting\"\ndescription: \"Deploy your documentation site with the Doccupine Platform or self-host on any platform that supports Next.js.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 11\n---\n\n# Deployment & Hosting\n\nThe fastest way to deploy your documentation is with the [Doccupine Platform](https://www.doccupine.com). If you prefer to manage your own infrastructure, you can self-host the generated Next.js app on any platform.\n\n## Doccupine Platform\n\nSign up at [doccupine.com](https://www.doccupine.com) and connect your repository. Your documentation site is live in minutes - no build configuration, no infrastructure to manage.\n\n<Callout type=\"success\">\n The Doccupine Platform is the recommended way to deploy. It handles builds, hosting, SSL, and updates automatically so you can focus on writing documentation.\n</Callout>\n\n### What you get\n\n- **Automatic deployments** on every push to your repository\n- **Site customization** through a visual dashboard - no code changes needed\n- **Team collaboration** so your whole team can manage docs together\n- **Custom domains** with automatic SSL\n- **AI Assistant and MCP server** included out of the box, no API key required\n- **Zero maintenance** - no servers, no build pipelines, no dependency updates\n\n### Getting started\n\n1. Create an account at [doccupine.com](https://www.doccupine.com).\n2. Connect your GitHub repository.\n3. Your site is deployed automatically.\n\nEvery push to your repository triggers a new deployment. You can customize your site's appearance, domain, and settings from the dashboard. See the [Platform Overview](/platform) for a full walkthrough of the dashboard, editor, and configuration options.\n\n---\n\n## Self-hosting\n\nDoccupine generates a standard Next.js app, so you can deploy it anywhere that supports Node.js or Next.js.\n\n<Callout type=\"warning\">\n Deploy the generated website directory (the Next.js app), not your MDX source directory. In a monorepo, set the root directory to the generated site directory.\n</Callout>\n\n<Callout type=\"note\">\n Self-hosting requires you to manage your own build pipeline, hosting, SSL certificates, and AI provider API keys. For a hands-off experience, consider the [Doccupine Platform](https://www.doccupine.com).\n</Callout>\n\n### Popular hosting options\n\n- **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app directory.\n- **Netlify** - supports Next.js via the `@netlify/plugin-nextjs` adapter. Works with the standard `next build` output.\n- **AWS Amplify** - fully managed hosting with CI/CD. Supports Next.js SSR out of the box.\n- **Cloudflare Pages** - deploy using the `@cloudflare/next-on-pages` adapter for edge-based hosting.\n- **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.\n- **Node.js server** - run `next build && next start` on any server or VPS with Node.js installed.\n\n### Sitemap and robots.txt\n\nDoccupine generates `sitemap.xml` and `robots.txt` automatically when you set a site URL. This is required for search engine indexing and is strongly recommended for any public deployment.\n\nSet the URL in `config.json`:\n\n```json\n{\n \"url\": \"https://docs.example.com\"\n}\n```\n\nAt deploy time, you can override the value with the `NEXT_PUBLIC_SITE_URL` environment variable - useful for preview deployments or staging environments.\n\n```bash\nNEXT_PUBLIC_SITE_URL=https://staging.example.com\n```\n\nThe generated sitemap includes every page from every [section](/sections), with `lastModified` derived from each page's frontmatter `date` or the file's modification time. When no URL is configured, the sitemap is skipped and `robots.txt` is emitted without a sitemap reference.\n\n### Troubleshooting\n\n- **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.\n- **Missing content** - verify your MDX files and assets are in the repository.\n- **SSR issues on edge platforms** - some features (like the AI chat API routes) require a Node.js runtime. Check your platform's documentation for SSR/API route support.";
@@ -43,7 +43,7 @@ Every push to your repository triggers a new deployment. You can customize your
43
43
  Doccupine generates a standard Next.js app, so you can deploy it anywhere that supports Node.js or Next.js.
44
44
 
45
45
  <Callout type="warning">
46
- Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the root directory to the generated site folder.
46
+ Deploy the generated website directory (the Next.js app), not your MDX source directory. In a monorepo, set the root directory to the generated site directory.
47
47
  </Callout>
48
48
 
49
49
  <Callout type="note">
@@ -52,7 +52,7 @@ Doccupine generates a standard Next.js app, so you can deploy it anywhere that s
52
52
 
53
53
  ### Popular hosting options
54
54
 
55
- - **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app folder.
55
+ - **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app directory.
56
56
  - **Netlify** - supports Next.js via the \`@netlify/plugin-nextjs\` adapter. Works with the standard \`next build\` output.
57
57
  - **AWS Amplify** - fully managed hosting with CI/CD. Supports Next.js SSR out of the box.
58
58
  - **Cloudflare Pages** - deploy using the \`@cloudflare/next-on-pages\` adapter for edge-based hosting.
@@ -1 +1 @@
1
- export declare const fontsMdxTemplate = "---\ntitle: \"Fonts\"\ndescription: \"Customize the documentation typography with a fonts.json file (Google Fonts or local custom fonts).\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 7\n---\n\n# Fonts\n\nDefine your site\u2019s typography with a `fonts.json` file. You can load fonts from Google Fonts or from local font files bundled with your project.\n\n## fonts.json\n\nPlace a `fonts.json` at your project root (the same folder where you execute `npx doccupine`). Choose one of the following configurations depending on the source of your fonts.\n\n## Google Fonts\n\nUse this when your font is hosted by Google Fonts.\n\n```json\n{\n \"googleFont\": {\n \"fontName\": \"Work_Sans\",\n \"subsets\": [\"latin\"],\n \"weight\": \"400\"\n }\n}\n```\n\n### Rules\n\n- **fontName**: Capitalize each word. If the name contains spaces, replace them with `_`.\n - Example: \"Work Sans\" \u2192 `Work_Sans`\n- **subsets**: Pick the subsets you need (for example, `latin`).\n- **weight**: The font weight to load - a single string (for example, `\"400\"`) or an array of strings (for example, `[\"400\", \"500\", \"700\"]`).\n\n<Callout type=\"warning\">\n The `fontName` must match the Google Fonts name exactly - capitalize each word and replace spaces with underscores.\n</Callout>\n\n### Tips\n\n- **One primary family**: Start with a single family and weight, then add more if needed.\n- **Readability**: Choose readable body fonts; reserve display fonts for headings.\n\n## Local custom fonts\n\nUse this when you want to ship your own font files. Upload your font files to the Next.js `public` directory, then reference them with relative paths.\n\n```json\n{\n \"localFonts\": {\n \"src\": [\n {\n \"path\": \"../public/font.woff\",\n \"weight\": \"400\",\n \"style\": \"normal\"\n },\n {\n \"path\": \"../public/font.woff\",\n \"weight\": \"400\",\n \"style\": \"italic\"\n },\n {\n \"path\": \"../public/font.woff\",\n \"weight\": \"700\",\n \"style\": \"normal\"\n },\n {\n \"path\": \"../public/font.woff\",\n \"weight\": \"700\",\n \"style\": \"italic\"\n }\n ]\n }\n}\n```\n\n### Single file shorthand\n\nIf you have only one file, you can use:\n\n```json\n{\n \"localFonts\": \"../public/font.woff\"\n}\n```\n\n### Rules\n\n- **Upload files**: Place `font.woff` (and any additional weights/styles) in your Next.js `public` directory.\n- **path**: Use a relative path to the file from where it is consumed by your build tooling; the example above uses `../public/font.woff`.\n- **weight**: String value of the font weight (for example, `\"400\"`, `\"700\"`).\n- **style**: `\"normal\"` or `\"italic\"`.\n\n### Tips\n\n- **Provide only what you need**: Include the minimal set of weights/styles to keep bundle size small.\n- **File formats**: `.woff` is broadly supported and recommended for the web.\n\n## Behavior\n\n- **Placement**: Put `fonts.json` in the project root alongside `config.json` and (optionally) `theme.json`.\n- **Validation**: Follow the naming rule for `googleFont.fontName` (capitalize, replace spaces with `_`).\n\n<Callout type=\"note\">\n When using local fonts, ensure the referenced files exist under the Next.js `public` directory. The paths in `fonts.json` are relative to where they are consumed by the build tooling.\n</Callout>\n\n## Examples\n\n- **Google Fonts (single weight)**\n\n```json\n{\n \"googleFont\": {\n \"fontName\": \"Work_Sans\",\n \"subsets\": [\"latin\"],\n \"weight\": \"400\"\n }\n}\n```\n\n- **Google Fonts (multiple weights)**\n\n```json\n{\n \"googleFont\": {\n \"fontName\": \"Work_Sans\",\n \"subsets\": [\"latin\"],\n \"weight\": [\"400\", \"500\", \"600\", \"700\"]\n }\n}\n```\n\n- **Local fonts (multiple weights/styles)**\n\n```json\n{\n \"localFonts\": {\n \"src\": [\n { \"path\": \"../public/font.woff\", \"weight\": \"400\", \"style\": \"normal\" },\n { \"path\": \"../public/font.woff\", \"weight\": \"400\", \"style\": \"italic\" },\n { \"path\": \"../public/font.woff\", \"weight\": \"700\", \"style\": \"normal\" },\n { \"path\": \"../public/font.woff\", \"weight\": \"700\", \"style\": \"italic\" }\n ]\n }\n}\n```";
1
+ export declare const fontsMdxTemplate = "---\ntitle: \"Fonts\"\ndescription: \"Customize the documentation typography with a fonts.json file (Google Fonts or local custom fonts).\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 7\n---\n\n# Fonts\n\nDefine your site\u2019s typography with a `fonts.json` file. You can load fonts from Google Fonts or from local font files bundled with your project.\n\n## fonts.json\n\nPlace a `fonts.json` at your project root (the same directory where you execute `npx doccupine`). Choose one of the following configurations depending on the source of your fonts.\n\n## Google Fonts\n\nUse this when your font is hosted by Google Fonts.\n\n```json\n{\n \"googleFont\": {\n \"fontName\": \"Work_Sans\",\n \"subsets\": [\"latin\"],\n \"weight\": \"400\"\n }\n}\n```\n\n### Rules\n\n- **fontName**: Capitalize each word. If the name contains spaces, replace them with `_`.\n - Example: \"Work Sans\" \u2192 `Work_Sans`\n- **subsets**: Pick the subsets you need (for example, `latin`).\n- **weight**: The font weight to load - a single string (for example, `\"400\"`) or an array of strings (for example, `[\"400\", \"500\", \"700\"]`).\n\n<Callout type=\"warning\">\n The `fontName` must match the Google Fonts name exactly - capitalize each word and replace spaces with underscores.\n</Callout>\n\n### Tips\n\n- **One primary family**: Start with a single family and weight, then add more if needed.\n- **Readability**: Choose readable body fonts; reserve display fonts for headings.\n\n## Local custom fonts\n\nUse this when you want to ship your own font files. Upload your font files to the Next.js `public` directory, then reference them with relative paths.\n\n```json\n{\n \"localFonts\": {\n \"src\": [\n {\n \"path\": \"../public/font.woff\",\n \"weight\": \"400\",\n \"style\": \"normal\"\n },\n {\n \"path\": \"../public/font.woff\",\n \"weight\": \"400\",\n \"style\": \"italic\"\n },\n {\n \"path\": \"../public/font.woff\",\n \"weight\": \"700\",\n \"style\": \"normal\"\n },\n {\n \"path\": \"../public/font.woff\",\n \"weight\": \"700\",\n \"style\": \"italic\"\n }\n ]\n }\n}\n```\n\n### Single file shorthand\n\nIf you have only one file, you can use:\n\n```json\n{\n \"localFonts\": \"../public/font.woff\"\n}\n```\n\n### Rules\n\n- **Upload files**: Place `font.woff` (and any additional weights/styles) in your Next.js `public` directory.\n- **path**: Use a relative path to the file from where it is consumed by your build tooling; the example above uses `../public/font.woff`.\n- **weight**: String value of the font weight (for example, `\"400\"`, `\"700\"`).\n- **style**: `\"normal\"` or `\"italic\"`.\n\n### Tips\n\n- **Provide only what you need**: Include the minimal set of weights/styles to keep bundle size small.\n- **File formats**: `.woff` is broadly supported and recommended for the web.\n\n## Behavior\n\n- **Placement**: Put `fonts.json` in the project root alongside `config.json` and (optionally) `theme.json`.\n- **Validation**: Follow the naming rule for `googleFont.fontName` (capitalize, replace spaces with `_`).\n\n<Callout type=\"note\">\n When using local fonts, ensure the referenced files exist under the Next.js `public` directory. The paths in `fonts.json` are relative to where they are consumed by the build tooling.\n</Callout>\n\n## Examples\n\n- **Google Fonts (single weight)**\n\n```json\n{\n \"googleFont\": {\n \"fontName\": \"Work_Sans\",\n \"subsets\": [\"latin\"],\n \"weight\": \"400\"\n }\n}\n```\n\n- **Google Fonts (multiple weights)**\n\n```json\n{\n \"googleFont\": {\n \"fontName\": \"Work_Sans\",\n \"subsets\": [\"latin\"],\n \"weight\": [\"400\", \"500\", \"600\", \"700\"]\n }\n}\n```\n\n- **Local fonts (multiple weights/styles)**\n\n```json\n{\n \"localFonts\": {\n \"src\": [\n { \"path\": \"../public/font.woff\", \"weight\": \"400\", \"style\": \"normal\" },\n { \"path\": \"../public/font.woff\", \"weight\": \"400\", \"style\": \"italic\" },\n { \"path\": \"../public/font.woff\", \"weight\": \"700\", \"style\": \"normal\" },\n { \"path\": \"../public/font.woff\", \"weight\": \"700\", \"style\": \"italic\" }\n ]\n }\n}\n```";
@@ -13,7 +13,7 @@ Define your site’s typography with a \`fonts.json\` file. You can load fonts f
13
13
 
14
14
  ## fonts.json
15
15
 
16
- Place a \`fonts.json\` at your project root (the same folder where you execute \`npx doccupine\`). Choose one of the following configurations depending on the source of your fonts.
16
+ Place a \`fonts.json\` at your project root (the same directory where you execute \`npx doccupine\`). Choose one of the following configurations depending on the source of your fonts.
17
17
 
18
18
  ## Google Fonts
19
19
 
@@ -1 +1 @@
1
- export declare const footerLinksMdxTemplate = "---\ntitle: \"Footer Links\"\ndescription: \"Add static links at the bottom of the documentation pages.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 4\n---\n\n# Footer Links\n\nAdd a row of static links at the bottom of your documentation pages, just above the footer. Links open in a new tab and are useful for pointing users to related resources, repositories, or external docs.\n\n## links.json\n\nPlace a `links.json` at your project root (the same folder where you execute `npx doccupine`). When present, Doccupine displays the links at the bottom of each page. You can add as many links as you need.\n\n### Example links.json\n\n```json\n[\n {\n \"title\": \"Back to Home\",\n \"url\": \"https://doccupine.com\",\n \"icon\": \"arrow-left\"\n },\n {\n \"title\": \"GitHub\",\n \"url\": \"https://github.com/doccupine\",\n \"icon\": \"git-branch\"\n },\n {\n \"title\": \"Discord\",\n \"url\": \"https://discord.gg/E9BufYGPhG\",\n \"icon\": \"message-circle\"\n }\n]\n```\n\n### Fields\n\n- **title**: The label shown for the link.\n- **url**: The destination URL. Links open in a new tab with `target=\"_blank\"` and `rel=\"noopener noreferrer\"`.\n- **icon**: The icon to display next to the label. Icons are from [Lucide](https://lucide.dev/).\n\n## Behavior\n\n- **Empty or missing file**: If `links.json` is empty or not present, the footer links are hidden.\n- **Order**: Links appear in the same order as in the array.\n- **No limit**: Add as many links as you want; they wrap automatically on smaller screens.";
1
+ export declare const footerLinksMdxTemplate = "---\ntitle: \"Footer Links\"\ndescription: \"Add static links at the bottom of the documentation pages.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 4\n---\n\n# Footer Links\n\nAdd a row of static links at the bottom of your documentation pages, just above the footer. Links open in a new tab and are useful for pointing users to related resources, repositories, or external docs.\n\n## links.json\n\nPlace a `links.json` at your project root (the same directory where you execute `npx doccupine`). When present, Doccupine displays the links at the bottom of each page. You can add as many links as you need.\n\n### Example links.json\n\n```json\n[\n {\n \"title\": \"Back to Home\",\n \"url\": \"https://doccupine.com\",\n \"icon\": \"arrow-left\"\n },\n {\n \"title\": \"GitHub\",\n \"url\": \"https://github.com/doccupine\",\n \"icon\": \"git-branch\"\n },\n {\n \"title\": \"Discord\",\n \"url\": \"https://discord.gg/E9BufYGPhG\",\n \"icon\": \"message-circle\"\n }\n]\n```\n\n### Fields\n\n- **title**: The label shown for the link.\n- **url**: The destination URL. Links open in a new tab with `target=\"_blank\"` and `rel=\"noopener noreferrer\"`.\n- **icon**: The icon to display next to the label. Icons are from [Lucide](https://lucide.dev/).\n\n## Behavior\n\n- **Empty or missing file**: If `links.json` is empty or not present, the footer links are hidden.\n- **Order**: Links appear in the same order as in the array.\n- **No limit**: Add as many links as you want; they wrap automatically on smaller screens.";
@@ -13,7 +13,7 @@ Add a row of static links at the bottom of your documentation pages, just above
13
13
 
14
14
  ## links.json
15
15
 
16
- Place a \`links.json\` at your project root (the same folder where you execute \`npx doccupine\`). When present, Doccupine displays the links at the bottom of each page. You can add as many links as you need.
16
+ Place a \`links.json\` at your project root (the same directory where you execute \`npx doccupine\`). When present, Doccupine displays the links at the bottom of each page. You can add as many links as you need.
17
17
 
18
18
  ### Example links.json
19
19
 
@@ -1 +1 @@
1
- export declare const globalsMdxTemplate = "---\ntitle: \"Globals\"\ndescription: \"Configure global settings for your documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\ncategoryIcon: \"settings\"\norder: 1\n---\n\n# Global Configuration\n\nUse a `config.json` file to define project\u2011wide metadata for your documentation site. These values are applied to every generated page unless a page overrides them in its own frontmatter.\n\n## config.json\n\nPlace a `config.json` at your project root (the same folder where you execute `npx doccupine`) to define global metadata for your documentation site.\n\n```json\n{\n \"name\": \"Doccupine\",\n \"description\": \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\",\n \"icon\": \"https://docs.doccupine.com/favicon.ico\",\n \"image\": \"https://docs.doccupine.com/preview.png\",\n \"url\": \"https://docs.example.com\"\n}\n```\n\n## Fields\n\nAll fields are optional. Doccupine uses sensible defaults when a field is not set.\n\n- **name**: The primary name of your documentation website. Displayed in the site title and used in various UI elements.\n- **description**: A concise summary of your project, used in site metadata (e.g., HTML meta description) and social previews when not overridden.\n- **icon**: The favicon for your site. You can provide a full URL or a relative path to an asset in your project.\n- **image**: The Open Graph image used when links to your docs are shared on social platforms. Accepts a full URL or a relative path.\n- **url**: The public URL of your deployed site. Used as the base URL for `sitemap.xml` and `robots.txt`. When omitted, no sitemap is generated. Can be overridden at deploy time with the `NEXT_PUBLIC_SITE_URL` environment variable.\n\n## Per-page overrides\n\nAny page can override global values by defining the matching key in its frontmatter. When present, the page's value takes precedence over `config.json` for that page only.\n\n| Frontmatter field | Overrides | Effect |\n| ----------------- | ------------- | ----------------------------------------------------------- |\n| **title** | - | Page title in metadata and Open Graph |\n| **description** | `description` | Meta description and Open Graph description |\n| **name** | `name` | Site name shown in the title suffix (e.g. \"Page - My Docs\") |\n| **icon** | `icon` | Favicon for this page |\n| **image** | `image` | Open Graph preview image |\n| **section** | - | Assigns the page to a [section](/sections) |\n| **sectionOrder** | - | Controls section position in the tab bar |\n| **sectionLabel** | - | Renames the default \"Docs\" tab (use on `index.mdx`) |\n\n<Callout type=\"note\">\n If a key is not specified in a page's frontmatter, Doccupine falls back to the corresponding value in `config.json`.\n</Callout>\n\nExample frontmatter in an `.mdx` file:\n\n```text\n---\ntitle: \"My Feature\"\ndescription: \"A focused description just for this page.\"\nname: \"My Product Docs\"\nicon: \"/custom-favicon.ico\"\nimage: \"/custom-preview.png\"\ndate: \"2026-02-19\"\ncategory: \"Guides\"\n---\n```";
1
+ export declare const globalsMdxTemplate = "---\ntitle: \"Globals\"\ndescription: \"Configure global settings for your documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\ncategoryIcon: \"settings\"\norder: 1\n---\n\n# Global Configuration\n\nUse a `config.json` file to define project\u2011wide metadata for your documentation site. These values are applied to every generated page unless a page overrides them in its own frontmatter.\n\n## config.json\n\nPlace a `config.json` at your project root (the same directory where you execute `npx doccupine`) to define global metadata for your documentation site.\n\n```json\n{\n \"name\": \"Doccupine\",\n \"description\": \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\",\n \"icon\": \"https://docs.doccupine.com/favicon.ico\",\n \"image\": \"https://docs.doccupine.com/preview.png\",\n \"url\": \"https://docs.example.com\"\n}\n```\n\n## Fields\n\nAll fields are optional. Doccupine uses sensible defaults when a field is not set.\n\n- **name**: The primary name of your documentation website. Displayed in the site title and used in various UI elements.\n- **description**: A concise summary of your project, used in site metadata (e.g., HTML meta description) and social previews when not overridden.\n- **icon**: The favicon for your site. You can provide a full URL or a relative path to an asset in your project.\n- **image**: The Open Graph image used when links to your docs are shared on social platforms. Accepts a full URL or a relative path.\n- **url**: The public URL of your deployed site. Used as the base URL for `sitemap.xml` and `robots.txt`. When omitted, no sitemap is generated. Can be overridden at deploy time with the `NEXT_PUBLIC_SITE_URL` environment variable.\n\n## Per-page overrides\n\nAny page can override global values by defining the matching key in its frontmatter. When present, the page's value takes precedence over `config.json` for that page only.\n\n| Frontmatter field | Overrides | Effect |\n| ----------------- | ------------- | ----------------------------------------------------------- |\n| **title** | - | Page title in metadata and Open Graph |\n| **description** | `description` | Meta description and Open Graph description |\n| **name** | `name` | Site name shown in the title suffix (e.g. \"Page - My Docs\") |\n| **icon** | `icon` | Favicon for this page |\n| **image** | `image` | Open Graph preview image |\n| **section** | - | Assigns the page to a [section](/sections) |\n| **sectionOrder** | - | Controls section position in the tab bar |\n| **sectionLabel** | - | Renames the default \"Docs\" tab (use on `index.mdx`) |\n\n<Callout type=\"note\">\n If a key is not specified in a page's frontmatter, Doccupine falls back to the corresponding value in `config.json`.\n</Callout>\n\nExample frontmatter in an `.mdx` file:\n\n```text\n---\ntitle: \"My Feature\"\ndescription: \"A focused description just for this page.\"\nname: \"My Product Docs\"\nicon: \"/custom-favicon.ico\"\nimage: \"/custom-preview.png\"\ndate: \"2026-02-19\"\ncategory: \"Guides\"\n---\n```";
@@ -15,7 +15,7 @@ Use a \`config.json\` file to define project‑wide metadata for your documentat
15
15
 
16
16
  ## config.json
17
17
 
18
- Place a \`config.json\` at your project root (the same folder where you execute \`npx doccupine\`) to define global metadata for your documentation site.
18
+ Place a \`config.json\` at your project root (the same directory where you execute \`npx doccupine\`) to define global metadata for your documentation site.
19
19
 
20
20
  \`\`\`json
21
21
  {
@@ -1 +1 @@
1
- export declare const indexMdxTemplate = "---\ntitle: \"Introduction\"\ndescription: \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\"\ndate: \"2026-02-19\"\ncategory: \"Getting Started\"\ncategoryOrder: 0\ncategoryIcon: \"rocket\"\norder: 0\n---\n\n# Welcome to Doccupine\n\nDoccupine turns a folder of MDX files into a beautiful, production-ready documentation website. Write in standard Markdown, use any of the built-in components, and get a fully themed site with AI-powered search and automatic navigation - all from one command.\n\n```bash\nnpx doccupine\n```\n\nThat's it. Doccupine prompts you for a source directory, generates your site, and starts a dev server at `http://localhost:3000`. Every edit rebuilds instantly.\n\n<Callout type=\"info\">\n Want a managed experience instead? The [Doccupine Platform](/platform) gives you a browser-based editor, one-click publishing, custom domains, and team collaboration - no local setup needed.\n</Callout>\n\n## How it works\n\nYou write `.mdx` files using Markdown and built-in components like this one. Doccupine watches your files, generates pages, builds the sidebar from your frontmatter, and handles theming, AI chat, and deployment config for you.\n\nEverything on this page - the callout above, the cards below, the code block - is a live example of what your docs can look like out of the box.\n\n<Columns cols={2}>\n <Card title=\"Start writing\" icon=\"pencil\" href=\"/components\">\n Rich docs from day one. 14+ components work out of the box - no imports needed.\n </Card>\n <Card title=\"Ship it\" icon=\"rocket\" href=\"/deployment-and-hosting\">\n Deploy to the Doccupine Platform or self-host on any platform that supports Next.js.\n </Card>\n</Columns>\n\n## Configure your site\n\nDoccupine works with no configuration, but when you're ready to customize, everything is controlled through simple JSON files in your project root.\n\n- [Global settings](/globals) - Site name, description, favicon, and preview image via `config.json`\n- [Navigation](/navigation) - Override the auto-generated sidebar with `navigation.json`\n- [Theme](/theme) - Colors, logos, and dark mode palette via `theme.json`\n- [Fonts](/fonts) - Google Fonts or local font files via `fonts.json`\n- [AI Assistant](/ai-assistant) - Enable the built-in chat with your own API key\n- [MCP Server](/model-context-protocol) - Let AI tools search your docs through `/api/mcp`";
1
+ export declare const indexMdxTemplate = "---\ntitle: \"Introduction\"\ndescription: \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\"\ndate: \"2026-02-19\"\ncategory: \"Getting Started\"\ncategoryOrder: 0\ncategoryIcon: \"rocket\"\norder: 0\n---\n\n# Welcome to Doccupine\n\nDoccupine turns a directory of MDX files into a beautiful, production-ready documentation website. Write in standard Markdown, use any of the built-in components, and get a fully themed site with AI-powered search and automatic navigation - all from one command.\n\n```bash\nnpx doccupine\n```\n\nThat's it. Doccupine prompts you for a source directory, generates your site, and starts a dev server at `http://localhost:3000`. Every edit rebuilds instantly.\n\n<Callout type=\"info\">\n Want a managed experience instead? The [Doccupine Platform](/platform) gives you a browser-based editor, one-click publishing, custom domains, and team collaboration - no local setup needed.\n</Callout>\n\n## How it works\n\nYou write `.mdx` files using Markdown and built-in components like this one. Doccupine watches your files, generates pages, builds the sidebar from your frontmatter, and handles theming, AI chat, and deployment config for you.\n\nEverything on this page - the callout above, the cards below, the code block - is a live example of what your docs can look like out of the box.\n\n<Columns cols={2}>\n <Card title=\"Start writing\" icon=\"pencil\" href=\"/components\">\n Rich docs from day one. 14+ components work out of the box - no imports needed.\n </Card>\n <Card title=\"Ship it\" icon=\"rocket\" href=\"/deployment-and-hosting\">\n Deploy to the Doccupine Platform or self-host on any platform that supports Next.js.\n </Card>\n</Columns>\n\n## Configure your site\n\nDoccupine works with no configuration, but when you're ready to customize, everything is controlled through simple JSON files in your project root.\n\n- [Global settings](/globals) - Site name, description, favicon, and preview image via `config.json`\n- [Navigation](/navigation) - Override the auto-generated sidebar with `navigation.json`\n- [Theme](/theme) - Colors, logos, and dark mode palette via `theme.json`\n- [Fonts](/fonts) - Google Fonts or local font files via `fonts.json`\n- [AI Assistant](/ai-assistant) - Enable the built-in chat with your own API key\n- [MCP Server](/model-context-protocol) - Let AI tools search your docs through `/api/mcp`";
@@ -11,7 +11,7 @@ order: 0
11
11
 
12
12
  # Welcome to Doccupine
13
13
 
14
- Doccupine turns a folder of MDX files into a beautiful, production-ready documentation website. Write in standard Markdown, use any of the built-in components, and get a fully themed site with AI-powered search and automatic navigation - all from one command.
14
+ Doccupine turns a directory of MDX files into a beautiful, production-ready documentation website. Write in standard Markdown, use any of the built-in components, and get a fully themed site with AI-powered search and automatic navigation - all from one command.
15
15
 
16
16
  \`\`\`bash
17
17
  npx doccupine
@@ -1 +1 @@
1
- export declare const mediaAndAssetsMdxTemplate = "---\ntitle: \"Media and assets\"\ndescription: \"Serve static files like images, favicons, fonts, and Open Graph previews from the public directory.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 6\n---\n\n# Media and assets\n\nDoccupine watches a `public` directory in your project root (the same folder where you execute `npx doccupine`) and copies its contents into the generated Next.js `public` folder. Use it to serve static files such as favicons, Open Graph preview images, custom fonts, or any other media your documentation needs.\n\n## The public directory\n\nCreate a `public` folder at your project root. Any file you place inside is automatically synced to the generated site and served from the root URL path.\n\n```text\nmy-docs/\n\u251C\u2500\u2500 public/\n\u2502 \u251C\u2500\u2500 favicon.ico\n\u2502 \u251C\u2500\u2500 og-image.png\n\u2502 \u251C\u2500\u2500 logo.svg\n\u2502 \u2514\u2500\u2500 fonts/\n\u2502 \u2514\u2500\u2500 custom-font.woff2\n\u251C\u2500\u2500 docs/\n\u2502 \u2514\u2500\u2500 index.mdx\n\u251C\u2500\u2500 config.json\n\u2514\u2500\u2500 theme.json\n```\n\n## How assets are served\n\nFiles in the `public` directory are available at the root of your deployed domain. The path inside `public` maps directly to the URL path.\n\n| File | URL |\n| -------------------------------- | ------------------------------------------------- |\n| `public/favicon.ico` | `https://your-domain.com/favicon.ico` |\n| `public/og-image.png` | `https://your-domain.com/og-image.png` |\n| `public/logo.svg` | `https://your-domain.com/logo.svg` |\n| `public/fonts/custom-font.woff2` | `https://your-domain.com/fonts/custom-font.woff2` |\n\n## Common use cases\n\n### Favicon\n\nDrop a `favicon.ico` into the `public` folder. Browsers pick it up automatically from the root path.\n\n```text\npublic/favicon.ico \u2192 https://your-domain.com/favicon.ico\n```\n\n### Open Graph preview image\n\nAdd an image for link previews on social media. Reference it in your `config.json` so Doccupine sets the correct meta tags.\n\n```text\npublic/og-image.png \u2192 https://your-domain.com/og-image.png\n```\n\n### Custom fonts\n\nPlace font files in `public` and reference them from your `fonts.json`. See the **Fonts** page for full configuration details.\n\n```text\npublic/fonts/custom-font.woff2 \u2192 https://your-domain.com/fonts/custom-font.woff2\n```\n\n### Images and other media\n\nAny image or file you want to reference in your MDX pages can live in `public`. Use a root-relative path in your content:\n\n```mdx\n![Architecture diagram](/architecture.png)\n```\n\n## Live syncing\n\n<Callout type=\"info\">\n Doccupine watches the `public` directory for changes while running. When you add, update, or remove a file, the generated site is updated automatically.\n</Callout>\n\n## Tips\n\n- **Keep it flat**: For a small number of files, placing them directly in `public` keeps paths short and simple.\n- **Use subfolders for organization**: For larger projects, group assets into folders like `public/fonts`, `public/images`, or `public/icons`.\n- **Mind file size**: Optimize images before adding them to keep deployment size and load times low.\n- **Consistent naming**: Use lowercase, hyphen-separated filenames (e.g., `og-image.png`) for predictable URLs.";
1
+ export declare const mediaAndAssetsMdxTemplate = "---\ntitle: \"Media and assets\"\ndescription: \"Serve static files like images, favicons, fonts, and Open Graph previews from the public directory.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 6\n---\n\n# Media and assets\n\nDoccupine watches a `public` directory in your project root (the same directory where you execute `npx doccupine`) and copies its contents into the generated Next.js `public` directory. Use it to serve static files such as favicons, Open Graph preview images, custom fonts, or any other media your documentation needs.\n\n## The public directory\n\nCreate a `public` directory at your project root. Any file you place inside is automatically synced to the generated site and served from the root URL path.\n\n```text\nmy-docs/\n\u251C\u2500\u2500 public/\n\u2502 \u251C\u2500\u2500 favicon.ico\n\u2502 \u251C\u2500\u2500 og-image.png\n\u2502 \u251C\u2500\u2500 logo.svg\n\u2502 \u2514\u2500\u2500 fonts/\n\u2502 \u2514\u2500\u2500 custom-font.woff2\n\u251C\u2500\u2500 docs/\n\u2502 \u2514\u2500\u2500 index.mdx\n\u251C\u2500\u2500 config.json\n\u2514\u2500\u2500 theme.json\n```\n\n## How assets are served\n\nFiles in the `public` directory are available at the root of your deployed domain. The path inside `public` maps directly to the URL path.\n\n| File | URL |\n| -------------------------------- | ------------------------------------------------- |\n| `public/favicon.ico` | `https://your-domain.com/favicon.ico` |\n| `public/og-image.png` | `https://your-domain.com/og-image.png` |\n| `public/logo.svg` | `https://your-domain.com/logo.svg` |\n| `public/fonts/custom-font.woff2` | `https://your-domain.com/fonts/custom-font.woff2` |\n\n## Common use cases\n\n### Favicon\n\nDrop a `favicon.ico` into the `public` directory. Browsers pick it up automatically from the root path.\n\n```text\npublic/favicon.ico \u2192 https://your-domain.com/favicon.ico\n```\n\n### Open Graph preview image\n\nAdd an image for link previews on social media. Reference it in your `config.json` so Doccupine sets the correct meta tags.\n\n```text\npublic/og-image.png \u2192 https://your-domain.com/og-image.png\n```\n\n### Custom fonts\n\nPlace font files in `public` and reference them from your `fonts.json`. See the **Fonts** page for full configuration details.\n\n```text\npublic/fonts/custom-font.woff2 \u2192 https://your-domain.com/fonts/custom-font.woff2\n```\n\n### Images and other media\n\nAny image or file you want to reference in your MDX pages can live in `public`. Use a root-relative path in your content:\n\n```mdx\n![Architecture diagram](/architecture.png)\n```\n\n## Live syncing\n\n<Callout type=\"info\">\n Doccupine watches the `public` directory for changes while running. When you add, update, or remove a file, the generated site is updated automatically.\n</Callout>\n\n## Tips\n\n- **Keep it flat**: For a small number of files, placing them directly in `public` keeps paths short and simple.\n- **Use subdirectories for organization**: For larger projects, group assets into directories like `public/fonts`, `public/images`, or `public/icons`.\n- **Mind file size**: Optimize images before adding them to keep deployment size and load times low.\n- **Consistent naming**: Use lowercase, hyphen-separated filenames (e.g., `og-image.png`) for predictable URLs.";
@@ -9,11 +9,11 @@ order: 6
9
9
 
10
10
  # Media and assets
11
11
 
12
- Doccupine watches a \`public\` directory in your project root (the same folder where you execute \`npx doccupine\`) and copies its contents into the generated Next.js \`public\` folder. Use it to serve static files such as favicons, Open Graph preview images, custom fonts, or any other media your documentation needs.
12
+ Doccupine watches a \`public\` directory in your project root (the same directory where you execute \`npx doccupine\`) and copies its contents into the generated Next.js \`public\` directory. Use it to serve static files such as favicons, Open Graph preview images, custom fonts, or any other media your documentation needs.
13
13
 
14
14
  ## The public directory
15
15
 
16
- Create a \`public\` folder at your project root. Any file you place inside is automatically synced to the generated site and served from the root URL path.
16
+ Create a \`public\` directory at your project root. Any file you place inside is automatically synced to the generated site and served from the root URL path.
17
17
 
18
18
  \`\`\`text
19
19
  my-docs/
@@ -44,7 +44,7 @@ Files in the \`public\` directory are available at the root of your deployed dom
44
44
 
45
45
  ### Favicon
46
46
 
47
- Drop a \`favicon.ico\` into the \`public\` folder. Browsers pick it up automatically from the root path.
47
+ Drop a \`favicon.ico\` into the \`public\` directory. Browsers pick it up automatically from the root path.
48
48
 
49
49
  \`\`\`text
50
50
  public/favicon.ico → https://your-domain.com/favicon.ico
@@ -83,6 +83,6 @@ Any image or file you want to reference in your MDX pages can live in \`public\`
83
83
  ## Tips
84
84
 
85
85
  - **Keep it flat**: For a small number of files, placing them directly in \`public\` keeps paths short and simple.
86
- - **Use subfolders for organization**: For larger projects, group assets into folders like \`public/fonts\`, \`public/images\`, or \`public/icons\`.
86
+ - **Use subdirectories for organization**: For larger projects, group assets into directories like \`public/fonts\`, \`public/images\`, or \`public/icons\`.
87
87
  - **Mind file size**: Optimize images before adding them to keep deployment size and load times low.
88
88
  - **Consistent naming**: Use lowercase, hyphen-separated filenames (e.g., \`og-image.png\`) for predictable URLs.`;
@@ -1 +1 @@
1
- export declare const navigationMdxTemplate = "---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n\n# Navigation\n\nDoccupine builds your sidebar automatically from your MDX pages. By default, it reads the page frontmatter and groups pages into categories in the order you define. For larger docs, you can take full control with a `navigation.json` file.\n\n## Automatic navigation (default)\n\nWhen no custom navigation is provided, Doccupine generates a structure based on each page's frontmatter.\n\n### Frontmatter fields\n\n- **category**: The category name that groups the page in the sidebar.\n- **categoryOrder**: The position of the category within the sidebar. Lower numbers appear first.\n- **order**: The position of the page within its category. Lower numbers appear first.\n- **navIcon**: Optional [Lucide](https://lucide.dev/icons) icon name shown next to the page's sidebar link.\n- **categoryIcon**: Optional Lucide icon name for the page's category header. The first page in a category that sets it wins.\n\n### Example frontmatter\n\n```text\n---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n```\n\nThis approach is great for small sets of documents. For larger projects, setting these fields on every page can become repetitive.\n\n## Custom navigation with navigation.json\n\nTo centrally define the entire sidebar, create a `navigation.json` at your project root (the same folder where you execute `npx doccupine`). When present, it takes priority over page frontmatter and fully controls the navigation structure.\n\n### Array format\n\nThe simplest format is an array of categories. When using [sections](/sections), this applies to the root section only.\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Introduction\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n },\n {\n \"label\": \"Components\",\n \"links\": [\n { \"slug\": \"components\", \"title\": \"Components\" },\n { \"slug\": \"headers-and-text\", \"title\": \"Headers and Text\" },\n { \"slug\": \"lists-and-tables\", \"title\": \"Lists and tables\" },\n { \"slug\": \"code\", \"title\": \"Code\" },\n { \"slug\": \"accordion\", \"title\": \"Accordion\" },\n { \"slug\": \"tabs\", \"title\": \"Tabs\" },\n { \"slug\": \"cards\", \"title\": \"Cards\" },\n { \"slug\": \"buttons\", \"title\": \"Buttons\" },\n { \"slug\": \"callouts\", \"title\": \"Callouts\" },\n { \"slug\": \"image-and-embeds\", \"title\": \"Images and embeds\" },\n { \"slug\": \"icons\", \"title\": \"Icons\" },\n { \"slug\": \"fields\", \"title\": \"Fields\" },\n { \"slug\": \"update\", \"title\": \"Update\" },\n { \"slug\": \"columns\", \"title\": \"Columns\" },\n { \"slug\": \"steps\", \"title\": \"Steps\" },\n { \"slug\": \"color-swatches\", \"title\": \"Color Swatches\" }\n ]\n },\n {\n \"label\": \"Configuration\",\n \"links\": [\n { \"slug\": \"globals\", \"title\": \"Globals\" },\n { \"slug\": \"navigation\", \"title\": \"Navigation\" },\n { \"slug\": \"sections\", \"title\": \"Sections\" },\n { \"slug\": \"footer-links\", \"title\": \"Footer Links\" },\n { \"slug\": \"theme\", \"title\": \"Theme\" },\n { \"slug\": \"media-and-assets\", \"title\": \"Media and assets\" },\n { \"slug\": \"fonts\", \"title\": \"Fonts\" },\n { \"slug\": \"ai-assistant\", \"title\": \"AI Assistant\" },\n { \"slug\": \"model-context-protocol\", \"title\": \"Model Context Protocol\" },\n { \"slug\": \"analytics\", \"title\": \"Analytics\" },\n { \"slug\": \"deployment-and-hosting\", \"title\": \"Deployment & Hosting\" },\n { \"slug\": \"authentication\", \"title\": \"Authentication\" }\n ]\n }\n]\n```\n\n### Object format (per-section)\n\nWhen using [sections](/sections), you can define navigation for each section by using an object keyed by section slug. Sections without a key fall back to auto-generated navigation from frontmatter.\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n }\n ],\n \"platform\": [\n {\n \"label\": \"Overview\",\n \"links\": [\n { \"slug\": \"platform/auth\", \"title\": \"Authentication\" },\n { \"slug\": \"platform/users\", \"title\": \"Users\" }\n ]\n }\n ]\n}\n```\n\nThe key `\"\"` controls the root section. Other keys match section slugs defined in `sections.json` or derived from frontmatter. See [Sections](/sections) for details on configuring sections.\n\n### Fields\n\n- **label**: The section header shown in the sidebar.\n- **icon**: Optional [Lucide](https://lucide.dev/icons) icon name shown next to the category header.\n- **links**: An array of page entries for that section.\n - **slug**: The MDX file slug (filename without extension). Use an empty string `\"\"` for `index.mdx`.\n - **title**: The display title in the navigation. This can differ from the page's `title` frontmatter.\n - **icon**: Optional Lucide icon name shown next to the link.\n - **links**: Optional array of nested child links. See [Nested navigation](#nested-navigation).\n\n## Icons\n\nAdd icons to categories and links to make the sidebar easier to scan. Icons use [Lucide](https://lucide.dev/icons) names in kebab-case (e.g. `rocket`, `book-open`, `settings`). Unknown names render nothing, so a typo never breaks the build.\n\nWith frontmatter, set `navIcon` on a page for its sidebar link and `categoryIcon` for its category:\n\n```text\n---\ntitle: \"Introduction\"\ncategory: \"Getting Started\"\ncategoryOrder: 1\norder: 1\nnavIcon: \"rocket\"\ncategoryIcon: \"book-open\"\n---\n```\n\nWith `navigation.json`, add an `icon` to any category or link:\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"icon\": \"book-open\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Introduction\", \"icon\": \"rocket\" },\n { \"slug\": \"commands\", \"title\": \"Commands\", \"icon\": \"terminal\" }\n ]\n }\n]\n```\n\n## Nested navigation\n\nA link in `navigation.json` can hold its own `links` array to create a collapsible group. Groups expand and collapse on click and open automatically when one of their pages is active. Nesting can go as deep as you need.\n\n```json\n[\n {\n \"label\": \"Guides\",\n \"icon\": \"book-open\",\n \"links\": [\n { \"slug\": \"guides\", \"title\": \"Overview\", \"icon\": \"compass\" },\n {\n \"title\": \"Advanced\",\n \"icon\": \"settings\",\n \"links\": [\n { \"slug\": \"guides/caching\", \"title\": \"Caching\" },\n { \"slug\": \"guides/streaming\", \"title\": \"Streaming\" }\n ]\n }\n ]\n }\n]\n```\n\nA group can be a plain label - omit `slug` and it acts only as a collapsible header - or a real page, by adding a `slug` so the group title is also a link.\n\n<Callout type=\"note\">\n Nested groups are only available through `navigation.json`. Frontmatter navigation is always two levels: category and pages.\n</Callout>\n\n## Precedence and behavior\n\n<Callout type=\"note\">\n `navigation.json` takes priority over frontmatter. If present, it fully controls the sidebar structure for the sections it covers.\n</Callout>\n\n- Without `navigation.json`, the sidebar is built from page frontmatter: `category` -> grouped; `categoryOrder` -> category position; `order` -> page position.\n- When using the object format, sections not listed in `navigation.json` fall back to frontmatter-based navigation.\n- Pages without a `category` appear at the top level.\n\n## Tips\n\n- **Start simple**: Use frontmatter for small docs. Switch to `navigation.json` as the structure grows.\n- **Keep slugs consistent**: `slug` must match the MDX filename (e.g., `text.mdx` -> `text`).\n- **Control titles**: Use `title` in `navigation.json` to customize sidebar labels without changing page frontmatter.\n- **Per-section navigation**: Use the object format to define different sidebars for each section. Mix and match - define some sections explicitly and let others auto-generate.";
1
+ export declare const navigationMdxTemplate = "---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n\n# Navigation\n\nDoccupine builds your sidebar automatically from your MDX pages. By default, it reads the page frontmatter and groups pages into categories in the order you define. For larger docs, you can take full control with a `navigation.json` file.\n\n## Automatic navigation (default)\n\nWhen no custom navigation is provided, Doccupine generates a structure based on each page's frontmatter.\n\n### Frontmatter fields\n\n- **category**: The category name that groups the page in the sidebar.\n- **categoryOrder**: The position of the category within the sidebar. Lower numbers appear first.\n- **order**: The position of the page within its category. Lower numbers appear first.\n- **navIcon**: Optional [Lucide](https://lucide.dev/icons) icon name shown next to the page's sidebar link.\n- **categoryIcon**: Optional Lucide icon name for the page's category header. The first page in a category that sets it wins.\n\n### Example frontmatter\n\n```text\n---\ntitle: \"Navigation\"\ndescription: \"Organize and structure your navigation.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 2\n---\n```\n\nThis approach is great for small sets of documents. For larger projects, setting these fields on every page can become repetitive.\n\n## Custom navigation with navigation.json\n\nTo centrally define the entire sidebar, create a `navigation.json` at your project root (the same directory where you execute `npx doccupine`). When present, it takes priority over page frontmatter and fully controls the navigation structure.\n\n### Array format\n\nThe simplest format is an array of categories. When using [sections](/sections), this applies to the root section only.\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Introduction\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n },\n {\n \"label\": \"Components\",\n \"links\": [\n { \"slug\": \"components\", \"title\": \"Components\" },\n { \"slug\": \"headers-and-text\", \"title\": \"Headers and Text\" },\n { \"slug\": \"lists-and-tables\", \"title\": \"Lists and tables\" },\n { \"slug\": \"code\", \"title\": \"Code\" },\n { \"slug\": \"accordion\", \"title\": \"Accordion\" },\n { \"slug\": \"tabs\", \"title\": \"Tabs\" },\n { \"slug\": \"cards\", \"title\": \"Cards\" },\n { \"slug\": \"buttons\", \"title\": \"Buttons\" },\n { \"slug\": \"callouts\", \"title\": \"Callouts\" },\n { \"slug\": \"image-and-embeds\", \"title\": \"Images and embeds\" },\n { \"slug\": \"icons\", \"title\": \"Icons\" },\n { \"slug\": \"fields\", \"title\": \"Fields\" },\n { \"slug\": \"update\", \"title\": \"Update\" },\n { \"slug\": \"columns\", \"title\": \"Columns\" },\n { \"slug\": \"steps\", \"title\": \"Steps\" },\n { \"slug\": \"color-swatches\", \"title\": \"Color Swatches\" }\n ]\n },\n {\n \"label\": \"Configuration\",\n \"links\": [\n { \"slug\": \"globals\", \"title\": \"Globals\" },\n { \"slug\": \"navigation\", \"title\": \"Navigation\" },\n { \"slug\": \"sections\", \"title\": \"Sections\" },\n { \"slug\": \"footer-links\", \"title\": \"Footer Links\" },\n { \"slug\": \"theme\", \"title\": \"Theme\" },\n { \"slug\": \"media-and-assets\", \"title\": \"Media and assets\" },\n { \"slug\": \"fonts\", \"title\": \"Fonts\" },\n { \"slug\": \"ai-assistant\", \"title\": \"AI Assistant\" },\n { \"slug\": \"model-context-protocol\", \"title\": \"Model Context Protocol\" },\n { \"slug\": \"analytics\", \"title\": \"Analytics\" },\n { \"slug\": \"deployment-and-hosting\", \"title\": \"Deployment & Hosting\" },\n { \"slug\": \"authentication\", \"title\": \"Authentication\" }\n ]\n }\n]\n```\n\n### Object format (per-section)\n\nWhen using [sections](/sections), you can define navigation for each section by using an object keyed by section slug. Sections without a key fall back to auto-generated navigation from frontmatter.\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Getting Started\" },\n { \"slug\": \"commands\", \"title\": \"Commands\" }\n ]\n }\n ],\n \"platform\": [\n {\n \"label\": \"Overview\",\n \"links\": [\n { \"slug\": \"platform/auth\", \"title\": \"Authentication\" },\n { \"slug\": \"platform/users\", \"title\": \"Users\" }\n ]\n }\n ]\n}\n```\n\nThe key `\"\"` controls the root section. Other keys match section slugs defined in `sections.json` or derived from frontmatter. See [Sections](/sections) for details on configuring sections.\n\n### Fields\n\n- **label**: The section header shown in the sidebar.\n- **icon**: Optional [Lucide](https://lucide.dev/icons) icon name shown next to the category header.\n- **links**: An array of page entries for that section.\n - **slug**: The MDX file slug (filename without extension). Use an empty string `\"\"` for `index.mdx`.\n - **title**: The display title in the navigation. This can differ from the page's `title` frontmatter.\n - **icon**: Optional Lucide icon name shown next to the link.\n - **links**: Optional array of nested child links. See [Nested navigation](#nested-navigation).\n\n## Icons\n\nAdd icons to categories and links to make the sidebar easier to scan. Icons use [Lucide](https://lucide.dev/icons) names in kebab-case (e.g. `rocket`, `book-open`, `settings`). Unknown names render nothing, so a typo never breaks the build.\n\nWith frontmatter, set `navIcon` on a page for its sidebar link and `categoryIcon` for its category:\n\n```text\n---\ntitle: \"Introduction\"\ncategory: \"Getting Started\"\ncategoryOrder: 1\norder: 1\nnavIcon: \"rocket\"\ncategoryIcon: \"book-open\"\n---\n```\n\nWith `navigation.json`, add an `icon` to any category or link:\n\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"icon\": \"book-open\",\n \"links\": [\n { \"slug\": \"\", \"title\": \"Introduction\", \"icon\": \"rocket\" },\n { \"slug\": \"commands\", \"title\": \"Commands\", \"icon\": \"terminal\" }\n ]\n }\n]\n```\n\n## Nested navigation\n\nA link in `navigation.json` can hold its own `links` array to create a collapsible group. Groups expand and collapse on click and open automatically when one of their pages is active. Nesting can go as deep as you need.\n\n```json\n[\n {\n \"label\": \"Guides\",\n \"icon\": \"book-open\",\n \"links\": [\n { \"slug\": \"guides\", \"title\": \"Overview\", \"icon\": \"compass\" },\n {\n \"title\": \"Advanced\",\n \"icon\": \"settings\",\n \"links\": [\n { \"slug\": \"guides/caching\", \"title\": \"Caching\" },\n { \"slug\": \"guides/streaming\", \"title\": \"Streaming\" }\n ]\n }\n ]\n }\n]\n```\n\nA group can be a plain label - omit `slug` and it acts only as a collapsible header - or a real page, by adding a `slug` so the group title is also a link.\n\n<Callout type=\"note\">\n Nested groups are only available through `navigation.json`. Frontmatter navigation is always two levels: category and pages.\n</Callout>\n\n## Precedence and behavior\n\n<Callout type=\"note\">\n `navigation.json` takes priority over frontmatter. If present, it fully controls the sidebar structure for the sections it covers.\n</Callout>\n\n- Without `navigation.json`, the sidebar is built from page frontmatter: `category` -> grouped; `categoryOrder` -> category position; `order` -> page position.\n- When using the object format, sections not listed in `navigation.json` fall back to frontmatter-based navigation.\n- Pages without a `category` appear at the top level.\n\n## Tips\n\n- **Start simple**: Use frontmatter for small docs. Switch to `navigation.json` as the structure grows.\n- **Keep slugs consistent**: `slug` must match the MDX filename (e.g., `text.mdx` -> `text`).\n- **Control titles**: Use `title` in `navigation.json` to customize sidebar labels without changing page frontmatter.\n- **Per-section navigation**: Use the object format to define different sidebars for each section. Mix and match - define some sections explicitly and let others auto-generate.";
@@ -40,7 +40,7 @@ This approach is great for small sets of documents. For larger projects, setting
40
40
 
41
41
  ## Custom navigation with navigation.json
42
42
 
43
- To centrally define the entire sidebar, create a \`navigation.json\` at your project root (the same folder where you execute \`npx doccupine\`). When present, it takes priority over page frontmatter and fully controls the navigation structure.
43
+ To centrally define the entire sidebar, create a \`navigation.json\` at your project root (the same directory where you execute \`npx doccupine\`). When present, it takes priority over page frontmatter and fully controls the navigation structure.
44
44
 
45
45
  ### Array format
46
46
 
@@ -1 +1 @@
1
- export declare const platformFileEditorMdxTemplate = "---\ntitle: \"File Editor\"\ndescription: \"Browse, create, and edit your documentation files directly in the browser.\"\ndate: \"2026-02-19\"\ncategory: \"Editing\"\ncategoryOrder: 1\ncategoryIcon: \"pencil\"\norder: 0\nsection: \"Platform\"\n---\n\n# File Editor\n\nThe file editor is the main workspace for your documentation project. It provides a browser-based file explorer and editor for working with your MDX files and assets.\n\n## File explorer\n\nThe left panel has three tabs:\n\n- **Files** - browse your repository's file tree, create and manage files and folders\n- **Media** - manage uploaded images and binary assets\n- **Navigation** - open the [Navigation Builder](/platform/navigation-settings) to configure your sidebar structure with drag-and-drop\n\nIn the Files tab, you can:\n\n- **Browse** directories and files\n- **Create** new files and folders\n- **Rename** and **delete** existing files\n- **Upload** binary assets like images, favicons, and font files\n\nClick any file to open it in the editor panel.\n\n## Editing files\n\nThe editor supports MDX files with full syntax highlighting. Changes you make are saved as **pending changes** - they aren't committed to your repository until you publish.\n\n<Callout type=\"note\">\n Pending changes are stored in Doccupine's database, not in your Git repository. This means you can make edits across multiple sessions before publishing.\n</Callout>\n\n## Version history\n\nFor any file, you can view its commit history to see how it has changed over time. This lets you:\n\n- See when changes were made and what the commit messages were\n- View the file's content at any previous commit\n- Compare past versions to understand what changed\n\n## Binary files\n\nYou can upload images and other binary assets (PNG, JPG, SVG, WOFF2, etc.) directly through the file explorer. These are stored temporarily in Doccupine's storage and committed to your repository when you publish.\n\n## Read-only mode\n\nTeam members with the **Viewer** or **Billing** role can browse files but cannot make edits. The editor will display content in read-only mode for these users.";
1
+ export declare const platformFileEditorMdxTemplate = "---\ntitle: \"File Editor\"\ndescription: \"Browse, create, and edit your documentation files directly in the browser.\"\ndate: \"2026-02-19\"\ncategory: \"Editing\"\ncategoryOrder: 1\ncategoryIcon: \"pencil\"\norder: 0\nsection: \"Platform\"\n---\n\n# File Editor\n\nThe file editor is the main workspace for your documentation project. It provides a browser-based file explorer and editor for working with your MDX files and assets.\n\n## File explorer\n\nThe left panel has three tabs:\n\n- **Files** - browse your repository's file tree, create and manage files and directories\n- **Media** - manage uploaded images and binary assets\n- **Navigation** - open the [Navigation Builder](/platform/navigation-settings) to configure your sidebar structure with drag-and-drop\n\nIn the Files tab, you can:\n\n- **Browse** directories and files\n- **Create** new files and directories\n- **Rename** and **delete** existing files\n- **Upload** binary assets like images, favicons, and font files\n\nClick any file to open it in the editor panel.\n\n## Editing files\n\nThe editor supports MDX files with full syntax highlighting. Changes you make are saved as **pending changes** - they aren't committed to your repository until you publish.\n\n<Callout type=\"note\">\n Pending changes are stored in Doccupine's database, not in your Git repository. This means you can make edits across multiple sessions before publishing.\n</Callout>\n\n## Version history\n\nFor any file, you can view its commit history to see how it has changed over time. This lets you:\n\n- See when changes were made and what the commit messages were\n- View the file's content at any previous commit\n- Compare past versions to understand what changed\n\n## Binary files\n\nYou can upload images and other binary assets (PNG, JPG, SVG, WOFF2, etc.) directly through the file explorer. These are stored temporarily in Doccupine's storage and committed to your repository when you publish.\n\n## Read-only mode\n\nTeam members with the **Viewer** or **Billing** role can browse files but cannot make edits. The editor will display content in read-only mode for these users.";
@@ -17,14 +17,14 @@ The file editor is the main workspace for your documentation project. It provide
17
17
 
18
18
  The left panel has three tabs:
19
19
 
20
- - **Files** - browse your repository's file tree, create and manage files and folders
20
+ - **Files** - browse your repository's file tree, create and manage files and directories
21
21
  - **Media** - manage uploaded images and binary assets
22
22
  - **Navigation** - open the [Navigation Builder](/platform/navigation-settings) to configure your sidebar structure with drag-and-drop
23
23
 
24
24
  In the Files tab, you can:
25
25
 
26
26
  - **Browse** directories and files
27
- - **Create** new files and folders
27
+ - **Create** new files and directories
28
28
  - **Rename** and **delete** existing files
29
29
  - **Upload** binary assets like images, favicons, and font files
30
30
 
@@ -1 +1 @@
1
- export declare const sectionsMdxTemplate = "---\ntitle: \"Sections\"\ndescription: \"Split your documentation into top-level sections with independent sidebars.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 3\n---\n\n# Sections\n\nSections let you divide your documentation into separate top-level areas - for example \"Guides\", \"API Reference\", and \"SDKs\". Each section gets its own sidebar and appears as a horizontal tab bar below the site header.\n\n<Callout type=\"note\">\n Sections are entirely opt-in. If you don't configure them, nothing changes - your site works exactly as before with a single flat navigation.\n</Callout>\n\n## Automatic sections (frontmatter)\n\nThe simplest way to add sections is through page frontmatter. Add a `section` field to group pages, and an optional `sectionOrder` to control the order of sections in the bar.\n\n### Frontmatter fields\n\n- **section**: The display name for the section this page belongs to (e.g. \"API Reference\").\n- **sectionOrder**: Controls the position of the section in the bar. Lower numbers appear first.\n\nThe section slug is derived automatically from the label - lowercased with spaces replaced by hyphens. So \"API Reference\" becomes `api-reference`.\n\nPages without a `section` field stay at the root URL and appear under a default tab labeled \"Docs\". You can rename this tab with the `sectionLabel` field on your `index.mdx`:\n\n```text\n---\ntitle: \"Welcome\"\nsectionLabel: \"Guides\"\n---\n```\n\n### Directory-based organization\n\nYou can organize each section's files in a subdirectory that matches the section slug. When the directory name matches, Doccupine automatically assigns the files to that section and strips the directory from the URL.\n\n```text\ndocs/\n index.mdx\n getting-started.mdx\n platform/\n index.mdx -> /platform\n auth.mdx -> /platform/auth\n```\n\nWhere `platform/index.mdx` has:\n\n```text\n---\ntitle: \"Platform Overview\"\nsection: \"Platform\"\nsectionOrder: 1\ncategory: \"Getting Started\"\n---\n```\n\nThe directory `platform/` matches the section slug `platform`, so it is stripped. `platform/index.mdx` serves at `/platform/` and `platform/auth.mdx` serves at `/platform/auth`.\n\n<Callout type=\"info\">\n Once a section exists, any file placed in a matching directory is automatically assigned to it - even without a `section` field in its own frontmatter. Only the first file needs `section` and `sectionOrder` to create the section. After that, the directory does the work.\n</Callout>\n\nFiles at the root level with a `section` field work too - they keep their full slug under the section prefix.\n\n### Section index pages\n\nIf a section has no index page (no file at its root URL), Doccupine generates a redirect to the first page in that section, sorted by `categoryOrder` then `order`.\n\n<Callout type=\"note\">\n You can override the auto-redirect by creating an `index.mdx` in the section's directory.\n</Callout>\n\n### Flat file example\n\nYou can also keep all files at the root and rely purely on frontmatter:\n\n```text\n---\ntitle: \"Authentication\"\nsection: \"API Reference\"\nsectionOrder: 2\ncategory: \"Auth\"\ncategoryOrder: 1\norder: 1\n---\n```\n\nThis page would be served at `/api-reference/authentication`.\n\n## Explicit sections with sections.json\n\nFor full control over slugs, create a `sections.json` file at your project root (the same folder where you run `npx doccupine`).\n\n### Minimal example\n\n```json\n[\n { \"label\": \"Docs\", \"slug\": \"\" },\n { \"label\": \"Platform\", \"slug\": \"platform\" }\n]\n```\n\nThis defines two sections. Pages are assigned automatically:\n\n- Files in a `platform/` directory belong to the \"Platform\" section (directory name matches slug).\n- Files with `section: \"Platform\"` in their frontmatter also belong to it.\n- Everything else stays in the root \"Docs\" section.\n\nNo `directory` field is needed when the directory name already matches the section slug.\n\n### Example with explicit directories\n\nWhen the directory name differs from the slug, use the `directory` field to map them:\n\n```json\n[\n { \"label\": \"Guides\", \"slug\": \"\", \"directory\": \"guides\" },\n { \"label\": \"API Reference\", \"slug\": \"api\", \"directory\": \"api-reference\" },\n { \"label\": \"SDKs\", \"slug\": \"sdks\", \"directory\": \"sdks\" }\n]\n```\n\n### Fields\n\n- **label**: The display name shown in the section bar.\n- **slug**: The URL prefix for this section. Use an empty string `\"\"` for the default section that serves at the root.\n- **directory** (optional): The subdirectory under your watch directory that contains this section's MDX files. Only needed when the directory name differs from the slug.\n\n### Directory structure example\n\nWith the explicit directory config above and a watch directory of `docs`, your files would look like:\n\n```text\ndocs/\n guides/\n index.mdx\n getting-started.mdx\n api-reference/\n authentication.mdx\n endpoints.mdx\n sdks/\n javascript.mdx\n python.mdx\n```\n\n## Section navigation\n\nEach section builds its own sidebar from the pages that belong to it. By default, pages are grouped by `category` and sorted by `categoryOrder` and `order` from frontmatter.\n\nFor explicit control, use `navigation.json` with the object format to define per-section navigation:\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [{ \"slug\": \"\", \"title\": \"Getting Started\" }]\n }\n ],\n \"platform\": [\n { \"label\": \"API\", \"links\": [{ \"slug\": \"platform/auth\", \"title\": \"Auth\" }] }\n ]\n}\n```\n\nKeys are section slugs. The root section uses `\"\"`. Sections without a key fall back to auto-generated navigation. See the Navigation page for the full format.\n\n## How pages are assigned to sections\n\nDoccupine checks these rules in order and uses the first match:\n\n1. **Explicit directory** - the file is inside a directory listed in a section's `directory` field.\n2. **Directory matches slug** - the file's parent directory matches a section slug (e.g. files in `platform/` match a section with `slug: \"platform\"`).\n3. **Frontmatter section field** - the file's `section` value matches a section label.\n4. **No match** - the page stays at the root.\n\n## Precedence for section discovery\n\n1. **sections.json exists** - Doccupine uses it to define available sections.\n2. **No sections.json but pages have `section` frontmatter** - Doccupine auto-discovers sections from the frontmatter. Sections update live as you add or remove the `section` field from files.\n3. **Neither** - No section bar appears. The site works exactly as before.\n\n## URL structure\n\nPages in the default section (with `slug: \"\"`) serve at the root:\n\n- Default section: `/getting-started`, `/installation`\n- Other sections: `/api/authentication`, `/sdks/javascript`\n\n<Callout type=\"info\">\n When a file is in a directory that matches its section slug, the directory is stripped so it doesn't appear twice. For example, `platform/auth.mdx` in the \"Platform\" section serves at `/platform/auth`, not `/platform/platform/auth`.\n</Callout>\n\n## sections.json vs navigation.json\n\nThese two config files serve different purposes and complement each other:\n\n- **sections.json** defines which sections exist - their labels, slugs, directory mappings, and order in the tab bar.\n- **navigation.json** controls the sidebar within each section - page ordering and grouping.\n\nYou can use either one independently. `sections.json` without `navigation.json` gives you sections with auto-generated sidebars. `navigation.json` without `sections.json` gives you custom sidebar ordering with frontmatter-discovered sections (or no sections at all).\n\n## Tips\n\n- **Start simple**: Add `section` and `sectionOrder` to a few pages to try it out. No config files needed.\n- **Use directories**: Organize each section's files in a directory that matches the section slug for clean URLs and a tidy file tree.\n- **Rename the default tab**: Add `sectionLabel: \"Your Label\"` to your `index.mdx` frontmatter. Defaults to \"Docs\" if omitted.\n- **Switch to sections.json**: When you need custom slugs or directory mappings that don't match section names, `sections.json` gives full control.\n- **Per-section navigation**: Use the object format in `navigation.json` to define custom sidebar ordering for specific sections.\n- **Independent sidebars**: Each section has its own sidebar. Previous/next navigation stays within the active section.";
1
+ export declare const sectionsMdxTemplate = "---\ntitle: \"Sections\"\ndescription: \"Split your documentation into top-level sections with independent sidebars.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 3\n---\n\n# Sections\n\nSections let you divide your documentation into separate top-level areas - for example \"Guides\", \"API Reference\", and \"SDKs\". Each section gets its own sidebar and appears as a horizontal tab bar below the site header.\n\n<Callout type=\"note\">\n Sections are entirely opt-in. If you don't configure them, nothing changes - your site works exactly as before with a single flat navigation.\n</Callout>\n\n## Automatic sections (frontmatter)\n\nThe simplest way to add sections is through page frontmatter. Add a `section` field to group pages, and an optional `sectionOrder` to control the order of sections in the bar.\n\n### Frontmatter fields\n\n- **section**: The display name for the section this page belongs to (e.g. \"API Reference\").\n- **sectionOrder**: Controls the position of the section in the bar. Lower numbers appear first.\n\nThe section slug is derived automatically from the label - lowercased with spaces replaced by hyphens. So \"API Reference\" becomes `api-reference`.\n\nPages without a `section` field stay at the root URL and appear under a default tab labeled \"Docs\". You can rename this tab with the `sectionLabel` field on your `index.mdx`:\n\n```text\n---\ntitle: \"Welcome\"\nsectionLabel: \"Guides\"\n---\n```\n\n### Directory-based organization\n\nYou can organize each section's files in a subdirectory that matches the section slug. When the directory name matches, Doccupine automatically assigns the files to that section and strips the directory from the URL.\n\n```text\ndocs/\n index.mdx\n getting-started.mdx\n platform/\n index.mdx -> /platform\n auth.mdx -> /platform/auth\n```\n\nWhere `platform/index.mdx` has:\n\n```text\n---\ntitle: \"Platform Overview\"\nsection: \"Platform\"\nsectionOrder: 1\ncategory: \"Getting Started\"\n---\n```\n\nThe directory `platform/` matches the section slug `platform`, so it is stripped. `platform/index.mdx` serves at `/platform/` and `platform/auth.mdx` serves at `/platform/auth`.\n\n<Callout type=\"info\">\n Once a section exists, any file placed in a matching directory is automatically assigned to it - even without a `section` field in its own frontmatter. Only the first file needs `section` and `sectionOrder` to create the section. After that, the directory does the work.\n</Callout>\n\nFiles at the root level with a `section` field work too - they keep their full slug under the section prefix.\n\n### Section index pages\n\nIf a section has no index page (no file at its root URL), Doccupine generates a redirect to the first page in that section, sorted by `categoryOrder` then `order`.\n\n<Callout type=\"note\">\n You can override the auto-redirect by creating an `index.mdx` in the section's directory.\n</Callout>\n\n### Flat file example\n\nYou can also keep all files at the root and rely purely on frontmatter:\n\n```text\n---\ntitle: \"Authentication\"\nsection: \"API Reference\"\nsectionOrder: 2\ncategory: \"Auth\"\ncategoryOrder: 1\norder: 1\n---\n```\n\nThis page would be served at `/api-reference/authentication`.\n\n## Explicit sections with sections.json\n\nFor full control over slugs, create a `sections.json` file at your project root (the same directory where you run `npx doccupine`).\n\n### Minimal example\n\n```json\n[\n { \"label\": \"Docs\", \"slug\": \"\" },\n { \"label\": \"Platform\", \"slug\": \"platform\" }\n]\n```\n\nThis defines two sections. Pages are assigned automatically:\n\n- Files in a `platform/` directory belong to the \"Platform\" section (directory name matches slug).\n- Files with `section: \"Platform\"` in their frontmatter also belong to it.\n- Everything else stays in the root \"Docs\" section.\n\nNo `directory` field is needed when the directory name already matches the section slug.\n\n### Example with explicit directories\n\nWhen the directory name differs from the slug, use the `directory` field to map them:\n\n```json\n[\n { \"label\": \"Guides\", \"slug\": \"\", \"directory\": \"guides\" },\n { \"label\": \"API Reference\", \"slug\": \"api\", \"directory\": \"api-reference\" },\n { \"label\": \"SDKs\", \"slug\": \"sdks\", \"directory\": \"sdks\" }\n]\n```\n\n### Fields\n\n- **label**: The display name shown in the section bar.\n- **slug**: The URL prefix for this section. Use an empty string `\"\"` for the default section that serves at the root.\n- **directory** (optional): The subdirectory under your watch directory that contains this section's MDX files. Only needed when the directory name differs from the slug.\n\n### Directory structure example\n\nWith the explicit directory config above and a watch directory of `docs`, your files would look like:\n\n```text\ndocs/\n guides/\n index.mdx\n getting-started.mdx\n api-reference/\n authentication.mdx\n endpoints.mdx\n sdks/\n javascript.mdx\n python.mdx\n```\n\n## Section navigation\n\nEach section builds its own sidebar from the pages that belong to it. By default, pages are grouped by `category` and sorted by `categoryOrder` and `order` from frontmatter.\n\nFor explicit control, use `navigation.json` with the object format to define per-section navigation:\n\n```json\n{\n \"\": [\n {\n \"label\": \"General\",\n \"links\": [{ \"slug\": \"\", \"title\": \"Getting Started\" }]\n }\n ],\n \"platform\": [\n { \"label\": \"API\", \"links\": [{ \"slug\": \"platform/auth\", \"title\": \"Auth\" }] }\n ]\n}\n```\n\nKeys are section slugs. The root section uses `\"\"`. Sections without a key fall back to auto-generated navigation. See the Navigation page for the full format.\n\n## How pages are assigned to sections\n\nDoccupine checks these rules in order and uses the first match:\n\n1. **Explicit directory** - the file is inside a directory listed in a section's `directory` field.\n2. **Directory matches slug** - the file's parent directory matches a section slug (e.g. files in `platform/` match a section with `slug: \"platform\"`).\n3. **Frontmatter section field** - the file's `section` value matches a section label.\n4. **No match** - the page stays at the root.\n\n## Precedence for section discovery\n\n1. **sections.json exists** - Doccupine uses it to define available sections.\n2. **No sections.json but pages have `section` frontmatter** - Doccupine auto-discovers sections from the frontmatter. Sections update live as you add or remove the `section` field from files.\n3. **Neither** - No section bar appears. The site works exactly as before.\n\n## URL structure\n\nPages in the default section (with `slug: \"\"`) serve at the root:\n\n- Default section: `/getting-started`, `/installation`\n- Other sections: `/api/authentication`, `/sdks/javascript`\n\n<Callout type=\"info\">\n When a file is in a directory that matches its section slug, the directory is stripped so it doesn't appear twice. For example, `platform/auth.mdx` in the \"Platform\" section serves at `/platform/auth`, not `/platform/platform/auth`.\n</Callout>\n\n## sections.json vs navigation.json\n\nThese two config files serve different purposes and complement each other:\n\n- **sections.json** defines which sections exist - their labels, slugs, directory mappings, and order in the tab bar.\n- **navigation.json** controls the sidebar within each section - page ordering and grouping.\n\nYou can use either one independently. `sections.json` without `navigation.json` gives you sections with auto-generated sidebars. `navigation.json` without `sections.json` gives you custom sidebar ordering with frontmatter-discovered sections (or no sections at all).\n\n## Tips\n\n- **Start simple**: Add `section` and `sectionOrder` to a few pages to try it out. No config files needed.\n- **Use directories**: Organize each section's files in a directory that matches the section slug for clean URLs and a tidy file tree.\n- **Rename the default tab**: Add `sectionLabel: \"Your Label\"` to your `index.mdx` frontmatter. Defaults to \"Docs\" if omitted.\n- **Switch to sections.json**: When you need custom slugs or directory mappings that don't match section names, `sections.json` gives full control.\n- **Per-section navigation**: Use the object format in `navigation.json` to define custom sidebar ordering for specific sections.\n- **Independent sidebars**: Each section has its own sidebar. Previous/next navigation stays within the active section.";
@@ -94,7 +94,7 @@ This page would be served at \`/api-reference/authentication\`.
94
94
 
95
95
  ## Explicit sections with sections.json
96
96
 
97
- For full control over slugs, create a \`sections.json\` file at your project root (the same folder where you run \`npx doccupine\`).
97
+ For full control over slugs, create a \`sections.json\` file at your project root (the same directory where you run \`npx doccupine\`).
98
98
 
99
99
  ### Minimal example
100
100
 
@@ -1 +1 @@
1
- export declare const themeMdxTemplate = "---\ntitle: \"Theme\"\ndescription: \"Customize the documentation UI colors with a theme.json file.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 5\n---\n\n# Theme\n\nDefine your site\u2019s color system with a `theme.json` file. This lets you tailor the look and feel of your documentation without changing content.\n\n## theme.json\n\nPlace a `theme.json` at your project root (the same folder where you execute `npx doccupine`). It supports multiple modes. Define a `default` mode and a `dark` mode.\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#93c5fd\",\n \"primary\": \"#2563eb\",\n \"primaryDark\": \"#1e40af\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#9bcaff\",\n \"primary\": \"#1e7ae0\",\n \"primaryDark\": \"#033d7e\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n },\n \"logo\": {\n \"dark\": \"https://docs.doccupine.com/logo-dark.svg\",\n \"light\": \"https://docs.doccupine.com/logo-light.svg\"\n }\n}\n```\n\n## Modes\n\n- **default**: The base color palette for your site.\n- **dark**: Dark\u2011mode palette.\n\n## Fields\n\n- **primaryLight**: A lighter variant of your brand color, used for subtle accents and backgrounds.\n- **primary**: The main brand color.\n- **primaryDark**: A darker variant of your brand color for emphasis and hover states.\n- **secondaryLight**: A lighter variant of your secondary color, used for subtle accents and backgrounds.\n- **secondary**: The secondary brand color used for highlights and UI accents.\n- **secondaryDark**: A darker variant of your secondary color for emphasis and hover states.\n- **tertiaryLight**: A lighter variant of your tertiary color, used for subtle accents and backgrounds.\n- **tertiary**: The tertiary accent color.\n- **tertiaryDark**: A darker variant of your tertiary color for emphasis and hover states.\n- **grayLight**: Light gray for surfaces and borders.\n- **gray**: Neutral gray for text and UI elements.\n- **grayDark**: Dark gray for headings or high\u2011contrast text.\n- **success**: Positive/confirmation color.\n- **error**: Error/destructive color.\n- **warning**: Warning/attention color.\n- **info**: Informational/highlight color.\n- **dark**: The darkest/base color (often page background in dark mode).\n- **light**: The lightest/base color (often page background in light mode).\n- **logo.light**: Path or URL to the logo used on light backgrounds. Recommended size: 164\u00D730 px.\n- **logo.dark**: Path or URL to the logo used on dark backgrounds. Recommended size: 164\u00D730 px.\n\n## Behavior\n\n- **Placement**: Put `theme.json` in the project root alongside `config.json`.\n- **Partial palettes**: If a key is missing in a mode, consumers may fall back to the `default` value.\n- **Logo size**: Recommended dimensions are 164px width and 30px height.\n\n<Callout type=\"warning\">\n Use valid hex colors (e.g., `#22c55e`). Invalid color values may cause unexpected rendering.\n</Callout>\n\n## Tips\n\n- **Contrast**: Ensure sufficient contrast between text and backgrounds for readability.\n- **Branding**: Start with your brand\u2019s `primary` color, then derive `primaryLight` and `primaryDark`.\n- **Iterate**: Adjust colors and refresh the site to preview changes quickly.\n\n# Demo\n\nIn the following demos, you can see how the theme can be changed. To override the theme, create a `theme.json` file in the project root and copy paste the code below.\n\n<DemoTheme variant=\"purple\" />\n<DemoTheme variant=\"green\" />\n<DemoTheme variant=\"yellow\" />\n<DemoTheme />\n\n## Purple\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#c4b5fd\",\n \"primary\": \"#8b5cf6\",\n \"primaryDark\": \"#5b21b6\",\n \"secondaryLight\": \"#86efac\",\n \"secondary\": \"#22c55e\",\n \"secondaryDark\": \"#15803d\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#ddd6fe\",\n \"primary\": \"#a78bfa\",\n \"primaryDark\": \"#7c3aed\",\n \"secondaryLight\": \"#6ee7b7\",\n \"secondary\": \"#10b981\",\n \"secondaryDark\": \"#065f46\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n\n<DemoTheme variant=\"purple\" />\n\n## Green\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#86efac\",\n \"primary\": \"#22c55e\",\n \"primaryDark\": \"#15803d\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#6ee7b7\",\n \"primary\": \"#10b981\",\n \"primaryDark\": \"#065f46\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n\n<DemoTheme variant=\"green\" />\n\n## Yellow\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#fbbf24\",\n \"primary\": \"#f59e0b\",\n \"primaryDark\": \"#d97706\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#86efac\",\n \"tertiary\": \"#22c55e\",\n \"tertiaryDark\": \"#15803d\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#fed7aa\",\n \"primary\": \"#fb923c\",\n \"primaryDark\": \"#ea580c\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#6ee7b7\",\n \"tertiary\": \"#10b981\",\n \"tertiaryDark\": \"#065f46\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n\n<DemoTheme variant=\"yellow\" />";
1
+ export declare const themeMdxTemplate = "---\ntitle: \"Theme\"\ndescription: \"Customize the documentation UI colors with a theme.json file.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 5\n---\n\n# Theme\n\nDefine your site\u2019s color system with a `theme.json` file. This lets you tailor the look and feel of your documentation without changing content.\n\n## theme.json\n\nPlace a `theme.json` at your project root (the same directory where you execute `npx doccupine`). It supports multiple modes. Define a `default` mode and a `dark` mode.\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#93c5fd\",\n \"primary\": \"#2563eb\",\n \"primaryDark\": \"#1e40af\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#9bcaff\",\n \"primary\": \"#1e7ae0\",\n \"primaryDark\": \"#033d7e\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n },\n \"logo\": {\n \"dark\": \"https://docs.doccupine.com/logo-dark.svg\",\n \"light\": \"https://docs.doccupine.com/logo-light.svg\"\n }\n}\n```\n\n## Modes\n\n- **default**: The base color palette for your site.\n- **dark**: Dark\u2011mode palette.\n\n## Fields\n\n- **primaryLight**: A lighter variant of your brand color, used for subtle accents and backgrounds.\n- **primary**: The main brand color.\n- **primaryDark**: A darker variant of your brand color for emphasis and hover states.\n- **secondaryLight**: A lighter variant of your secondary color, used for subtle accents and backgrounds.\n- **secondary**: The secondary brand color used for highlights and UI accents.\n- **secondaryDark**: A darker variant of your secondary color for emphasis and hover states.\n- **tertiaryLight**: A lighter variant of your tertiary color, used for subtle accents and backgrounds.\n- **tertiary**: The tertiary accent color.\n- **tertiaryDark**: A darker variant of your tertiary color for emphasis and hover states.\n- **grayLight**: Light gray for surfaces and borders.\n- **gray**: Neutral gray for text and UI elements.\n- **grayDark**: Dark gray for headings or high\u2011contrast text.\n- **success**: Positive/confirmation color.\n- **error**: Error/destructive color.\n- **warning**: Warning/attention color.\n- **info**: Informational/highlight color.\n- **dark**: The darkest/base color (often page background in dark mode).\n- **light**: The lightest/base color (often page background in light mode).\n- **logo.light**: Path or URL to the logo used on light backgrounds. Recommended size: 164\u00D730 px.\n- **logo.dark**: Path or URL to the logo used on dark backgrounds. Recommended size: 164\u00D730 px.\n\n## Behavior\n\n- **Placement**: Put `theme.json` in the project root alongside `config.json`.\n- **Partial palettes**: If a key is missing in a mode, consumers may fall back to the `default` value.\n- **Logo size**: Recommended dimensions are 164px width and 30px height.\n\n<Callout type=\"warning\">\n Use valid hex colors (e.g., `#22c55e`). Invalid color values may cause unexpected rendering.\n</Callout>\n\n## Tips\n\n- **Contrast**: Ensure sufficient contrast between text and backgrounds for readability.\n- **Branding**: Start with your brand\u2019s `primary` color, then derive `primaryLight` and `primaryDark`.\n- **Iterate**: Adjust colors and refresh the site to preview changes quickly.\n\n# Demo\n\nIn the following demos, you can see how the theme can be changed. To override the theme, create a `theme.json` file in the project root and copy paste the code below.\n\n<DemoTheme variant=\"purple\" />\n<DemoTheme variant=\"green\" />\n<DemoTheme variant=\"yellow\" />\n<DemoTheme />\n\n## Purple\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#c4b5fd\",\n \"primary\": \"#8b5cf6\",\n \"primaryDark\": \"#5b21b6\",\n \"secondaryLight\": \"#86efac\",\n \"secondary\": \"#22c55e\",\n \"secondaryDark\": \"#15803d\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#ddd6fe\",\n \"primary\": \"#a78bfa\",\n \"primaryDark\": \"#7c3aed\",\n \"secondaryLight\": \"#6ee7b7\",\n \"secondary\": \"#10b981\",\n \"secondaryDark\": \"#065f46\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n\n<DemoTheme variant=\"purple\" />\n\n## Green\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#86efac\",\n \"primary\": \"#22c55e\",\n \"primaryDark\": \"#15803d\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#fbbf24\",\n \"tertiary\": \"#f59e0b\",\n \"tertiaryDark\": \"#d97706\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#6ee7b7\",\n \"primary\": \"#10b981\",\n \"primaryDark\": \"#065f46\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#fed7aa\",\n \"tertiary\": \"#fb923c\",\n \"tertiaryDark\": \"#ea580c\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n\n<DemoTheme variant=\"green\" />\n\n## Yellow\n\n```json\n{\n \"default\": {\n \"primaryLight\": \"#fbbf24\",\n \"primary\": \"#f59e0b\",\n \"primaryDark\": \"#d97706\",\n \"secondaryLight\": \"#c4b5fd\",\n \"secondary\": \"#8b5cf6\",\n \"secondaryDark\": \"#5b21b6\",\n \"tertiaryLight\": \"#86efac\",\n \"tertiary\": \"#22c55e\",\n \"tertiaryDark\": \"#15803d\",\n \"grayLight\": \"#f3f4f6\",\n \"gray\": \"#9ca3af\",\n \"grayDark\": \"#374151\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#000000\",\n \"light\": \"#ffffff\"\n },\n \"dark\": {\n \"primaryLight\": \"#fed7aa\",\n \"primary\": \"#fb923c\",\n \"primaryDark\": \"#ea580c\",\n \"secondaryLight\": \"#ddd6fe\",\n \"secondary\": \"#a78bfa\",\n \"secondaryDark\": \"#7c3aed\",\n \"tertiaryLight\": \"#6ee7b7\",\n \"tertiary\": \"#10b981\",\n \"tertiaryDark\": \"#065f46\",\n \"grayLight\": \"#1f2937\",\n \"gray\": \"#6b7280\",\n \"grayDark\": \"#9ca3af\",\n \"success\": \"#10b981\",\n \"error\": \"#f43f5e\",\n \"warning\": \"#f59e0b\",\n \"info\": \"#3b82f6\",\n \"dark\": \"#ffffff\",\n \"light\": \"#000000\"\n }\n}\n```\n\n<DemoTheme variant=\"yellow\" />";
@@ -13,7 +13,7 @@ Define your site’s color system with a \`theme.json\` file. This lets you tail
13
13
 
14
14
  ## theme.json
15
15
 
16
- Place a \`theme.json\` at your project root (the same folder where you execute \`npx doccupine\`). It supports multiple modes. Define a \`default\` mode and a \`dark\` mode.
16
+ Place a \`theme.json\` at your project root (the same directory where you execute \`npx doccupine\`). It supports multiple modes. Define a \`default\` mode and a \`dark\` mode.
17
17
 
18
18
  \`\`\`json
19
19
  {
@@ -23,7 +23,7 @@ export const packageJsonTemplate = JSON.stringify({
23
23
  minisearch: "^7.2.0",
24
24
  next: "16.2.10",
25
25
  "next-mdx-remote": "^6.0.0",
26
- "posthog-js": "^1.396.6",
26
+ "posthog-js": "^1.398.0",
27
27
  "posthog-node": "^5.39.4",
28
28
  react: "19.2.7",
29
29
  "react-dom": "19.2.7",
@@ -1 +1 @@
1
- export declare const searchServiceTemplate = "import MiniSearch from \"minisearch\";\nimport { listDocs } from \"@/services/mcp/tools\";\n\ninterface IndexedDoc {\n id: string;\n slug: string;\n title: string;\n content: string;\n}\n\nexport interface SearchHit {\n slug: string;\n snippet: string;\n}\n\nlet index: MiniSearch<IndexedDoc> | null = null;\nlet docs: IndexedDoc[] = [];\nlet buildPromise: Promise<void> | null = null;\n\nconst CONTEXT_BEFORE = 60;\nconst CONTEXT_AFTER = 90;\n\nasync function ensureIndex(): Promise<MiniSearch<IndexedDoc>> {\n if (index) return index;\n if (buildPromise) {\n await buildPromise;\n return index!;\n }\n\n buildPromise = (async () => {\n const resources = await listDocs();\n\n docs = resources.map((doc) => {\n const slug =\n doc.path.replace(/^app\\//, \"\").replace(/\\/page\\.\\w+$/, \"\") || \"\";\n const cleanContent = doc.content\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .slice(0, 200_000);\n return {\n id: slug || \"__index__\",\n slug,\n title: doc.name,\n content: cleanContent,\n };\n });\n\n index = new MiniSearch<IndexedDoc>({\n fields: [\"title\", \"content\"],\n storeFields: [\"slug\", \"title\", \"content\"],\n searchOptions: {\n boost: { title: 3 },\n fuzzy: 0.2,\n prefix: true,\n },\n });\n\n index.addAll(docs);\n })();\n\n await buildPromise;\n return index!;\n}\n\nfunction extractSnippet(content: string, query: string): string {\n const lower = content.toLowerCase();\n const qLower = query.toLowerCase();\n const matchIdx = lower.indexOf(qLower);\n\n if (matchIdx === -1) {\n // Fuzzy match - no exact substring. Return start of content.\n const end = Math.min(content.length, CONTEXT_BEFORE + CONTEXT_AFTER);\n const raw = content.slice(0, end).trim();\n return raw.length < content.length ? raw + \"...\" : raw;\n }\n\n let start = Math.max(0, matchIdx - CONTEXT_BEFORE);\n let end = Math.min(content.length, matchIdx + query.length + CONTEXT_AFTER);\n\n // Align to word boundaries\n if (start > 0) {\n const space = content.indexOf(\" \", start);\n if (space !== -1 && space < matchIdx) start = space + 1;\n }\n if (end < content.length) {\n const space = content.lastIndexOf(\" \", end);\n if (space > matchIdx + query.length) end = space;\n }\n\n const prefix = start > 0 ? \"...\" : \"\";\n const suffix = end < content.length ? \"...\" : \"\";\n const snippet = content.slice(start, end).replace(/\\n+/g, \" \").trim();\n\n return prefix + snippet + suffix;\n}\n\nexport async function searchContent(\n query: string,\n limit = 10,\n): Promise<SearchHit[]> {\n const q = query.trim();\n if (!q) return [];\n\n const idx = await ensureIndex();\n const results = idx.search(q);\n\n return results.slice(0, limit).map((result) => {\n const doc = docs.find((d) => d.id === result.id);\n const content = doc?.content || \"\";\n return {\n slug: doc?.slug || \"\",\n snippet: extractSnippet(content, q),\n };\n });\n}\n";
1
+ export declare const searchServiceTemplate = "import MiniSearch from \"minisearch\";\nimport { listDocs } from \"@/services/mcp/tools\";\n\ninterface IndexedDoc {\n id: string;\n slug: string;\n title: string;\n content: string;\n}\n\nexport interface SearchHit {\n slug: string;\n snippet: string;\n}\n\nlet index: MiniSearch<IndexedDoc> | null = null;\nlet docs: IndexedDoc[] = [];\nlet buildPromise: Promise<void> | null = null;\n\nconst CONTEXT_BEFORE = 60;\nconst CONTEXT_AFTER = 90;\n\n/**\n * Convert raw MDX (as extracted from the generated page templates) into plain,\n * human-readable text for search snippets: undoes the template-literal escaping\n * and strips common Markdown syntax so inline code shows as Test rather than a\n * backtick-wrapped or backslash-escaped fragment.\n */\nfunction toPlainText(text: string): string {\n return (\n text\n // Undo the template-literal escaping from the generated page files so\n // escaped backticks, dollar-braces and backslashes read as plain text.\n .replace(/\\\\([\\s\\S])/g, \"$1\")\n // Images and links -> visible text only.\n .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\")\n .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\")\n // Bold markers.\n .replace(/\\*\\*([\\s\\S]*?)\\*\\*/g, \"$1\")\n // Line-leading markers: headings, blockquotes, list bullets.\n .replace(/^\\s{0,3}#{1,6}\\s+/gm, \"\")\n .replace(/^\\s{0,3}>\\s?/gm, \"\")\n .replace(/^\\s{0,3}[-*+]\\s+/gm, \"\")\n .replace(/^\\s{0,3}\\d+\\.\\s+/gm, \"\")\n // Inline code / escaped backticks -> plain text.\n .replace(/`/g, \"\")\n );\n}\n\nasync function ensureIndex(): Promise<MiniSearch<IndexedDoc>> {\n if (index) return index;\n if (buildPromise) {\n await buildPromise;\n return index!;\n }\n\n buildPromise = (async () => {\n const resources = await listDocs();\n\n docs = resources.map((doc) => {\n // Strip the leading \"app/\", the \"/page.ext\" suffix, and any Next.js\n // route-group segments like \"(site)\" which never appear in URLs. This\n // must match the URL slug produced by toDocPath() in mcp/tools.ts and\n // the nav slugs in the layout, otherwise content hits fail to map back\n // to a page on the client and get silently dropped.\n const slug = doc.path\n .replace(/^app\\//, \"\")\n .replace(/\\/page\\.\\w+$/, \"\")\n .split(\"/\")\n .filter((seg) => !(seg.startsWith(\"(\") && seg.endsWith(\")\")))\n .join(\"/\");\n const cleanContent = toPlainText(doc.content.replace(/\\r\\n/g, \"\\n\"))\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .slice(0, 200_000);\n return {\n id: slug || \"__index__\",\n slug,\n title: doc.name,\n content: cleanContent,\n };\n });\n\n index = new MiniSearch<IndexedDoc>({\n fields: [\"title\", \"content\"],\n storeFields: [\"slug\", \"title\", \"content\"],\n searchOptions: {\n boost: { title: 3 },\n fuzzy: 0.2,\n prefix: true,\n },\n });\n\n index.addAll(docs);\n })();\n\n await buildPromise;\n return index!;\n}\n\nfunction extractSnippet(content: string, query: string): string {\n const lower = content.toLowerCase();\n const qLower = query.toLowerCase();\n const matchIdx = lower.indexOf(qLower);\n\n if (matchIdx === -1) {\n // Fuzzy match - no exact substring. Return start of content.\n const end = Math.min(content.length, CONTEXT_BEFORE + CONTEXT_AFTER);\n const raw = content.slice(0, end).trim();\n return raw.length < content.length ? raw + \"...\" : raw;\n }\n\n let start = Math.max(0, matchIdx - CONTEXT_BEFORE);\n let end = Math.min(content.length, matchIdx + query.length + CONTEXT_AFTER);\n\n // Align to word boundaries\n if (start > 0) {\n const space = content.indexOf(\" \", start);\n if (space !== -1 && space < matchIdx) start = space + 1;\n }\n if (end < content.length) {\n const space = content.lastIndexOf(\" \", end);\n if (space > matchIdx + query.length) end = space;\n }\n\n const prefix = start > 0 ? \"...\" : \"\";\n const suffix = end < content.length ? \"...\" : \"\";\n const snippet = content.slice(start, end).replace(/\\n+/g, \" \").trim();\n\n return prefix + snippet + suffix;\n}\n\nexport async function searchContent(\n query: string,\n limit = 10,\n): Promise<SearchHit[]> {\n const q = query.trim();\n if (!q) return [];\n\n const idx = await ensureIndex();\n const results = idx.search(q);\n\n return results.slice(0, limit).map((result) => {\n const doc = docs.find((d) => d.id === result.id);\n const content = doc?.content || \"\";\n return {\n slug: doc?.slug || \"\",\n snippet: extractSnippet(content, q),\n };\n });\n}\n";
@@ -20,6 +20,33 @@ let buildPromise: Promise<void> | null = null;
20
20
  const CONTEXT_BEFORE = 60;
21
21
  const CONTEXT_AFTER = 90;
22
22
 
23
+ /**
24
+ * Convert raw MDX (as extracted from the generated page templates) into plain,
25
+ * human-readable text for search snippets: undoes the template-literal escaping
26
+ * and strips common Markdown syntax so inline code shows as Test rather than a
27
+ * backtick-wrapped or backslash-escaped fragment.
28
+ */
29
+ function toPlainText(text: string): string {
30
+ return (
31
+ text
32
+ // Undo the template-literal escaping from the generated page files so
33
+ // escaped backticks, dollar-braces and backslashes read as plain text.
34
+ .replace(/\\\\([\\s\\S])/g, "$1")
35
+ // Images and links -> visible text only.
36
+ .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, "$1")
37
+ .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, "$1")
38
+ // Bold markers.
39
+ .replace(/\\*\\*([\\s\\S]*?)\\*\\*/g, "$1")
40
+ // Line-leading markers: headings, blockquotes, list bullets.
41
+ .replace(/^\\s{0,3}#{1,6}\\s+/gm, "")
42
+ .replace(/^\\s{0,3}>\\s?/gm, "")
43
+ .replace(/^\\s{0,3}[-*+]\\s+/gm, "")
44
+ .replace(/^\\s{0,3}\\d+\\.\\s+/gm, "")
45
+ // Inline code / escaped backticks -> plain text.
46
+ .replace(/\`/g, "")
47
+ );
48
+ }
49
+
23
50
  async function ensureIndex(): Promise<MiniSearch<IndexedDoc>> {
24
51
  if (index) return index;
25
52
  if (buildPromise) {
@@ -31,10 +58,18 @@ async function ensureIndex(): Promise<MiniSearch<IndexedDoc>> {
31
58
  const resources = await listDocs();
32
59
 
33
60
  docs = resources.map((doc) => {
34
- const slug =
35
- doc.path.replace(/^app\\//, "").replace(/\\/page\\.\\w+$/, "") || "";
36
- const cleanContent = doc.content
37
- .replace(/\\r\\n/g, "\\n")
61
+ // Strip the leading "app/", the "/page.ext" suffix, and any Next.js
62
+ // route-group segments like "(site)" which never appear in URLs. This
63
+ // must match the URL slug produced by toDocPath() in mcp/tools.ts and
64
+ // the nav slugs in the layout, otherwise content hits fail to map back
65
+ // to a page on the client and get silently dropped.
66
+ const slug = doc.path
67
+ .replace(/^app\\//, "")
68
+ .replace(/\\/page\\.\\w+$/, "")
69
+ .split("/")
70
+ .filter((seg) => !(seg.startsWith("(") && seg.endsWith(")")))
71
+ .join("/");
72
+ const cleanContent = toPlainText(doc.content.replace(/\\r\\n/g, "\\n"))
38
73
  .replace(/\\n{3,}/g, "\\n\\n")
39
74
  .slice(0, 200_000);
40
75
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.107",
3
+ "version": "0.0.109",
4
4
  "description": "Free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {