doccupine 0.0.67 → 0.0.69

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.
@@ -93,7 +93,6 @@ import { platformCreatingAProjectMdxTemplate } from "../templates/mdx/platform/c
93
93
  import { platformSiteSettingsMdxTemplate } from "../templates/mdx/platform/site-settings.mdx.js";
94
94
  import { platformThemeSettingsMdxTemplate } from "../templates/mdx/platform/theme-settings.mdx.js";
95
95
  import { platformNavigationSettingsMdxTemplate } from "../templates/mdx/platform/navigation-settings.mdx.js";
96
- import { platformSectionsSettingsMdxTemplate } from "../templates/mdx/platform/sections-settings.mdx.js";
97
96
  import { platformFontsSettingsMdxTemplate } from "../templates/mdx/platform/fonts-settings.mdx.js";
98
97
  import { platformExternalLinksMdxTemplate } from "../templates/mdx/platform/external-links.mdx.js";
99
98
  import { platformAiAssistantMdxTemplate } from "../templates/mdx/platform/ai-assistant.mdx.js";
@@ -200,7 +199,6 @@ export const startingDocsStructure = {
200
199
  "platform/site-settings.mdx": platformSiteSettingsMdxTemplate,
201
200
  "platform/theme-settings.mdx": platformThemeSettingsMdxTemplate,
202
201
  "platform/navigation-settings.mdx": platformNavigationSettingsMdxTemplate,
203
- "platform/sections-settings.mdx": platformSectionsSettingsMdxTemplate,
204
202
  "platform/fonts-settings.mdx": platformFontsSettingsMdxTemplate,
205
203
  "platform/external-links.mdx": platformExternalLinksMdxTemplate,
206
204
  "platform/ai-assistant.mdx": platformAiAssistantMdxTemplate,
@@ -1 +1 @@
1
- export declare const docsTemplate = "import { Flex } from \"cherry-styled-components\";\nimport {\n DocsContainer,\n StyledMarkdownContainer,\n} from \"@/components/layout/DocsComponents\";\nimport { MDXRemote } from \"next-mdx-remote/rsc\";\nimport remarkGfm from \"remark-gfm\";\nimport { useMDXComponents } from \"@/components/MDXComponents\";\nimport { DocsSideBar } from \"@/components/DocsSideBar\";\nimport { ActionBar } from \"@/components/layout/ActionBar\";\n\ninterface DocsProps {\n content: string;\n}\n\ninterface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\nfunction extractHeadings(content: string): Heading[] {\n const contentWithoutCodeBlocks = content.replace(/```[\\s\\S]*?```/g, \"\");\n const headingRegex = /^(#{1,6})\\s+(.+)$/gm;\n const headings: Heading[] = [];\n let match;\n\n while ((match = headingRegex.exec(contentWithoutCodeBlocks)) !== null) {\n const level = match[1].length;\n const text = match[2].trim();\n const id = generateId(text);\n headings.push({ id, text, level });\n }\n\n return headings;\n}\n\nfunction Docs({ content }: DocsProps) {\n const headings = extractHeadings(content);\n const components = useMDXComponents({});\n\n return (\n <>\n <DocsContainer>\n <ActionBar content={content}>\n <Flex $gap={20}>\n <StyledMarkdownContainer>\n {content && (\n <MDXRemote\n source={content}\n options={{\n blockJS: false,\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n },\n }}\n components={components}\n />\n )}\n </StyledMarkdownContainer>\n </Flex>\n </ActionBar>\n </DocsContainer>\n <DocsSideBar headings={headings} />\n </>\n );\n}\n\nexport { Docs };\n";
1
+ export declare const docsTemplate = "import React from \"react\";\nimport { Flex } from \"cherry-styled-components\";\nimport {\n DocsContainer,\n StyledMarkdownContainer,\n StyledMissingComponent,\n} from \"@/components/layout/DocsComponents\";\nimport { MDXRemote } from \"next-mdx-remote/rsc\";\nimport remarkGfm from \"remark-gfm\";\nimport { useMDXComponents } from \"@/components/MDXComponents\";\nimport { DocsSideBar } from \"@/components/DocsSideBar\";\nimport { ActionBar } from \"@/components/layout/ActionBar\";\n\ninterface DocsProps {\n content: string;\n}\n\ninterface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\nfunction extractHeadings(content: string): Heading[] {\n const contentWithoutCodeBlocks = content.replace(/```[\\s\\S]*?```/g, \"\");\n const headingRegex = /^(#{1,6})\\s+(.+)$/gm;\n const headings: Heading[] = [];\n let match;\n\n while ((match = headingRegex.exec(contentWithoutCodeBlocks)) !== null) {\n const level = match[1].length;\n const text = match[2].trim();\n const id = generateId(text);\n headings.push({ id, text, level });\n }\n\n return headings;\n}\n\nfunction extractComponentNames(source: string): string[] {\n const stripped = source\n .replace(/```[\\s\\S]*?```/g, \"\")\n .replace(/`[^`]*`/g, \"\");\n const tagRegex = /<([A-Z][a-zA-Z0-9]*)/g;\n const names = new Set<string>();\n let match;\n while ((match = tagRegex.exec(stripped)) !== null) {\n names.add(match[1]);\n }\n return Array.from(names);\n}\n\nfunction MissingComponent({\n componentName,\n children,\n}: {\n componentName: string;\n children?: React.ReactNode;\n}) {\n return (\n <StyledMissingComponent>\n Missing component: &lt;{componentName} /&gt;\n </StyledMissingComponent>\n );\n}\n\nfunction Docs({ content }: DocsProps) {\n const headings = extractHeadings(content);\n const components = useMDXComponents({});\n\n const knownNames = Object.keys(components);\n const usedNames = extractComponentNames(content);\n const missingNames = usedNames.filter((name) => !knownNames.includes(name));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const stubs: Record<string, React.ComponentType<any>> = {};\n for (const name of missingNames) {\n stubs[name] = ({ children }: { children?: React.ReactNode }) => (\n <MissingComponent componentName={name}>{children}</MissingComponent>\n );\n }\n\n const allComponents = { ...components, ...stubs };\n\n return (\n <>\n <DocsContainer>\n <ActionBar content={content}>\n <Flex $gap={20}>\n <StyledMarkdownContainer>\n {content && (\n <MDXRemote\n source={content}\n options={{\n blockJS: false,\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n },\n }}\n components={allComponents}\n />\n )}\n </StyledMarkdownContainer>\n </Flex>\n </ActionBar>\n </DocsContainer>\n <DocsSideBar headings={headings} />\n </>\n );\n}\n\nexport { Docs };\n";
@@ -1,7 +1,9 @@
1
- export const docsTemplate = `import { Flex } from "cherry-styled-components";
1
+ export const docsTemplate = `import React from "react";
2
+ import { Flex } from "cherry-styled-components";
2
3
  import {
3
4
  DocsContainer,
4
5
  StyledMarkdownContainer,
6
+ StyledMissingComponent,
5
7
  } from "@/components/layout/DocsComponents";
6
8
  import { MDXRemote } from "next-mdx-remote/rsc";
7
9
  import remarkGfm from "remark-gfm";
@@ -43,10 +45,51 @@ function extractHeadings(content: string): Heading[] {
43
45
  return headings;
44
46
  }
45
47
 
48
+ function extractComponentNames(source: string): string[] {
49
+ const stripped = source
50
+ .replace(/\`\`\`[\\s\\S]*?\`\`\`/g, "")
51
+ .replace(/\`[^\`]*\`/g, "");
52
+ const tagRegex = /<([A-Z][a-zA-Z0-9]*)/g;
53
+ const names = new Set<string>();
54
+ let match;
55
+ while ((match = tagRegex.exec(stripped)) !== null) {
56
+ names.add(match[1]);
57
+ }
58
+ return Array.from(names);
59
+ }
60
+
61
+ function MissingComponent({
62
+ componentName,
63
+ children,
64
+ }: {
65
+ componentName: string;
66
+ children?: React.ReactNode;
67
+ }) {
68
+ return (
69
+ <StyledMissingComponent>
70
+ Missing component: &lt;{componentName} /&gt;
71
+ </StyledMissingComponent>
72
+ );
73
+ }
74
+
46
75
  function Docs({ content }: DocsProps) {
47
76
  const headings = extractHeadings(content);
48
77
  const components = useMDXComponents({});
49
78
 
79
+ const knownNames = Object.keys(components);
80
+ const usedNames = extractComponentNames(content);
81
+ const missingNames = usedNames.filter((name) => !knownNames.includes(name));
82
+
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ const stubs: Record<string, React.ComponentType<any>> = {};
85
+ for (const name of missingNames) {
86
+ stubs[name] = ({ children }: { children?: React.ReactNode }) => (
87
+ <MissingComponent componentName={name}>{children}</MissingComponent>
88
+ );
89
+ }
90
+
91
+ const allComponents = { ...components, ...stubs };
92
+
50
93
  return (
51
94
  <>
52
95
  <DocsContainer>
@@ -62,7 +105,7 @@ function Docs({ content }: DocsProps) {
62
105
  remarkPlugins: [remarkGfm],
63
106
  },
64
107
  }}
65
- components={components}
108
+ components={allComponents}
66
109
  />
67
110
  )}
68
111
  </StyledMarkdownContainer>
@@ -1 +1 @@
1
- export declare const columnsTemplate = "\"use client\";\nimport styled from \"styled-components\";\nimport { mq, Theme } from \"cherry-styled-components\";\n\nconst StyledColumns = styled.div<{ theme: Theme; $columns?: number }>`\n display: flex;\n flex-direction: column;\n gap: 20px;\n\n ${mq(\"lg\")} {\n display: grid;\n grid-template-columns: repeat(${({ $columns }) => $columns ?? 1}, 1fr);\n }\n`;\n\ninterface ColumnsProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n cols?: number;\n}\n\nfunction Columns({ children, cols }: ColumnsProps) {\n return <StyledColumns $columns={cols}>{children}</StyledColumns>;\n}\n\nexport { Columns };\n";
1
+ export declare const columnsTemplate = "\"use client\";\nimport styled from \"styled-components\";\nimport { mq, Theme } from \"cherry-styled-components\";\n\nconst StyledColumns = styled.div<{ theme: Theme; $columns?: number }>`\n display: flex;\n flex-direction: column;\n gap: 20px;\n\n ${mq(\"lg\")} {\n ${({ $columns }) =>\n $columns && $columns >= 3\n ? `display: grid; grid-template-columns: repeat(2, 1fr);`\n : \"\"}\n }\n\n ${mq(\"xl\")} {\n display: grid;\n grid-template-columns: repeat(${({ $columns }) => $columns ?? 1}, 1fr);\n }\n`;\n\ninterface ColumnsProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n cols?: number;\n}\n\nfunction Columns({ children, cols }: ColumnsProps) {\n return <StyledColumns $columns={cols}>{children}</StyledColumns>;\n}\n\nexport { Columns };\n";
@@ -8,6 +8,13 @@ const StyledColumns = styled.div<{ theme: Theme; $columns?: number }>\`
8
8
  gap: 20px;
9
9
 
10
10
  \${mq("lg")} {
11
+ \${({ $columns }) =>
12
+ $columns && $columns >= 3
13
+ ? \`display: grid; grid-template-columns: repeat(2, 1fr);\`
14
+ : ""}
15
+ }
16
+
17
+ \${mq("xl")} {
11
18
  display: grid;
12
19
  grid-template-columns: repeat(\${({ $columns }) => $columns ?? 1}, 1fr);
13
20
  }
@@ -1 +1 @@
1
- export declare const docsComponentsTemplate = "\"use client\";\nimport { darken, lighten, rgba } from \"polished\";\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.div<{ 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 }) => rgba(theme.colors.primaryLight, 0.2)};\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 $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 20px 80px 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\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 }) => rgba(theme.colors.primaryLight, 0.05)};\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 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\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) =>\n theme.isDark\n ? lighten(0.1, theme.colors.primaryLight)\n : darken(0.1, theme.colors.primaryDark)};\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.grayDark : theme.colors.primary};\n padding: 5px 0 5px 20px;\n display: flex;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n `\n\t\t\tcolor: ${theme.isDark ? lighten(0.1, theme.colors.primaryLight) : darken(0.1, theme.colors.primaryDark)};\n\t\t\tborder-color: ${theme.colors.primary};\n\t\t\tfont-weight: 600;\n\t`};\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 }) =>\n theme.isDark\n ? rgba(theme.colors.grayLight, 0.7)\n : rgba(theme.colors.light, 0.7)};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n padding: 20px;\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\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 }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\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\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 { darken, lighten, rgba } from \"polished\";\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.div<{ 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 }) => rgba(theme.colors.primaryLight, 0.2)};\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 $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 20px 80px 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\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 }) => rgba(theme.colors.primaryLight, 0.05)};\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 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\nexport const StyledStrong = styled.strong<{ theme: Theme }>`\n font-weight: 600;\n ${({ theme }) => styledStrong(theme)};\n color: ${({ theme }) =>\n theme.isDark\n ? lighten(0.1, theme.colors.primaryLight)\n : darken(0.1, theme.colors.primaryDark)};\n`;\n\nexport const StyledSidebarListItem = styled.li`\n display: flex;\n gap: 10px;\n clear: both;\n`;\n\nexport const StyledSidebarListItemLink = styled(Link)<Props>`\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1.6;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.grayDark : theme.colors.primary};\n padding: 5px 0 5px 20px;\n display: flex;\n transition: all 0.3s ease;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n ${({ $isActive, theme }) =>\n $isActive &&\n `\n\t\t\tcolor: ${theme.isDark ? lighten(0.1, theme.colors.primaryLight) : darken(0.1, theme.colors.primaryDark)};\n\t\t\tborder-color: ${theme.colors.primary};\n\t\t\tfont-weight: 600;\n\t`};\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 }) =>\n theme.isDark\n ? rgba(theme.colors.grayLight, 0.7)\n : rgba(theme.colors.light, 0.7)};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n padding: 20px;\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\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 }) =>\n theme.isDark ? theme.colors.dark : theme.colors.primary};\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 }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\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";
@@ -362,6 +362,19 @@ export const StyledMobileBurger = styled.span<Props>\`
362
362
  \`};
363
363
  \`;
364
364
 
365
+ export const StyledMissingComponent = styled.div\`
366
+ background: \${({ theme }) => theme.colors.error};
367
+ border-radius: \${({ theme }) => theme.spacing.radius.lg};
368
+ padding: 20px;
369
+ font-size: \${({ theme }) => theme.fontSizes.small.lg};
370
+ color: \${({ theme }) =>
371
+ theme.isDark ? theme.colors.dark : theme.colors.light};
372
+ font-weight: 600;
373
+ display: flex;
374
+ gap: 10px;
375
+ align-items: center;
376
+ \`;
377
+
365
378
  interface DocsWrapperProps {
366
379
  children: React.ReactNode;
367
380
  }
@@ -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# Media and assets\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\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 index.mdx\n\u251C\u2500\u2500 config.json\n\u2514\u2500\u2500 theme.json\n```\n\n## How assets are served\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\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\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\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\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- **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# Media and assets\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\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\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\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\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\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\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- **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.";
@@ -20,7 +20,8 @@ my-docs/
20
20
  │ ├── logo.svg
21
21
  │ └── fonts/
22
22
  │ └── custom-font.woff2
23
- ├── index.mdx
23
+ ├── docs/
24
+ │ └── index.mdx
24
25
  ├── config.json
25
26
  └── theme.json
26
27
  \`\`\`
@@ -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\norder: 0\nsection: \"Platform\"\n---\n# File Editor\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\nThe left panel shows your repository's file tree. 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\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\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\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\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\norder: 0\nsection: \"Platform\"\n---\n# File Editor\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\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\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\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\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\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.";
@@ -11,7 +11,13 @@ section: "Platform"
11
11
  The 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.
12
12
 
13
13
  ## File explorer
14
- The left panel shows your repository's file tree. You can:
14
+ The left panel has three tabs:
15
+
16
+ - **Files** - browse your repository's file tree, create and manage files and folders
17
+ - **Media** - manage uploaded images and binary assets
18
+ - **Navigation** - open the [Navigation Builder](/platform/navigation-settings) to configure your sidebar structure with drag-and-drop
19
+
20
+ In the Files tab, you can:
15
21
 
16
22
  - **Browse** directories and files
17
23
  - **Create** new files and folders
@@ -1 +1 @@
1
- export declare const platformNavigationSettingsMdxTemplate = "---\ntitle: \"Navigation Settings\"\ndescription: \"Define the sidebar structure for your documentation site with categories and links.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 2\norder: 2\nsection: \"Platform\"\n---\n# Navigation Settings\nThe Navigation settings page lets you define your sidebar structure through a visual interface. Organize your pages into categories and control the order they appear.\n\n## Structure\nNavigation is organized into **categories**, each containing one or more **links**:\n\n- **Category** - a group label in the sidebar (e.g., \"Getting Started\", \"API Reference\")\n- **Link** - an entry within a category, defined by a page slug and display title\n\n## Managing categories\n- Click **Add Category** to create a new group\n- Use the **up/down arrows** to reorder categories\n- Click the **remove** button to delete a category and its links\n\n## Managing links\nWithin each category:\n- Click **Add Link** to add a new page entry\n- Set the **slug** to the page's URL path (e.g., `getting-started`)\n- Set the **title** to the display text shown in the sidebar\n- Click the **remove** button to delete a link\n\n## Per-section navigation\nIf your site uses [sections](/sections), the Navigation settings page shows a tab bar for each section. You can configure independent sidebar structures for each section.\n\n<Callout type=\"note\">\n Sections without a custom navigation configuration fall back to auto-generated navigation based on page frontmatter (`category`, `categoryOrder`, and `order` fields).\n</Callout>\n\n## Auto-generated vs. manual\nIf you don't configure navigation at all, Doccupine automatically builds your sidebar from page frontmatter. The Navigation settings page is only needed when you want explicit control over the order and grouping.\n\n## How it works\nNavigation settings are stored in `navigation.json` at the root of your repository. The file supports two formats:\n\n**Array format** for single-section sites:\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [\n { \"slug\": \"getting-started\", \"title\": \"Quick Start\" }\n ]\n }\n]\n```\n\n**Object format** for multi-section sites:\n```json\n{\n \"\": [\n { \"label\": \"General\", \"links\": [{ \"slug\": \"\", \"title\": \"Introduction\" }] }\n ],\n \"api\": [\n { \"label\": \"Auth\", \"links\": [{ \"slug\": \"api/auth\", \"title\": \"Authentication\" }] }\n ]\n}\n```";
1
+ export declare const platformNavigationSettingsMdxTemplate = "---\ntitle: \"Navigation Settings\"\ndescription: \"Define the sidebar structure for your documentation site using the drag-and-drop Navigation Builder.\"\ndate: \"2026-02-24\"\ncategory: \"Configuration\"\ncategoryOrder: 2\norder: 2\nsection: \"Platform\"\n---\n# Navigation Settings\nThe Navigation Builder lets you define your sidebar structure through a visual, drag-and-drop interface. It lives inside the **File Explorer** as a dedicated **Navigation** tab, alongside the Files and Media tabs.\n\n## Structure\nNavigation is organized into three levels:\n\n- **Section** - a top-level area of your site (e.g., \"Docs\", \"API Reference\"). Each section appears as a tab below the site header and has its own sidebar. Sections are defined by a label, URL slug, and docs directory.\n- **Category** - a group label within a section's sidebar (e.g., \"Getting Started\")\n- **Link** - an individual page entry within a category, defined by a slug and title\n\n## Drag-and-drop reordering\nYou can reorder items at every level by dragging their handles:\n\n- **Drag categories** between sections or within the same section to change their position\n- **Drag links** between categories or within the same category to reorder them\n\n## Managing sections\n- Click **Add Section** in the toolbar to create a new section. Fill in the label, slug, and directory fields.\n- Click the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory.\n- Delete a section from its edit modal using the delete button. The root section cannot be deleted.\n\n### The default section\nOne section should have an empty slug (`\"\"`). This is the default/root section that serves pages at the root URL. Pages not assigned to any other section belong here.\n\n### Frontmatter-based sections\nYou can also define sections purely through page frontmatter without using the Navigation Builder. Add a `section` field to your MDX frontmatter and Doccupine will create sections automatically. See the [Sections documentation](/sections) for details.\n\n## Managing categories\n- Click **Add Category** within a section to create a new group\n- Click the **edit** button on a category to open its edit modal, where you can rename it or delete it\n\n## Adding links\nWithin each category, click **Add files** to open a popover that lists available MDX files not yet included in the navigation. You can:\n\n- **Search** by file name to filter the list\n- **Select** one or more files using checkboxes\n- Click **Add** to insert them as links in the category\n\nTo remove a link, click the **delete** button next to it.\n\n## Regenerate from files\nThe toolbar includes a **Regenerate** button that rebuilds the entire navigation tree from your MDX files' frontmatter (`category`, `categoryOrder`, and `order` fields). A confirmation modal appears before any existing manual navigation is replaced.\n\n<Callout type=\"warning\">\n Regenerating from files replaces your current navigation structure. This cannot be undone.\n</Callout>\n\n## Auto-generated vs. manual\nIf you don't configure navigation at all, Doccupine automatically builds your sidebar from page frontmatter. The Navigation Builder is only needed when you want explicit control over the order and grouping.\n\n## How it works\nWhen you save, the Navigation Builder writes two files to your repository as pending changes:\n\n- `navigation.json` - the category and link structure for each section\n- `sections.json` - the list of sections with their labels, slugs, and directories\n\n**Array format** for single-section sites (`navigation.json`):\n```json\n[\n {\n \"label\": \"Getting Started\",\n \"links\": [\n { \"slug\": \"getting-started\", \"title\": \"Quick Start\" }\n ]\n }\n]\n```\n\n**Object format** for multi-section sites (`navigation.json`):\n```json\n{\n \"\": [\n { \"label\": \"General\", \"links\": [{ \"slug\": \"\", \"title\": \"Introduction\" }] }\n ],\n \"api\": [\n { \"label\": \"Auth\", \"links\": [{ \"slug\": \"api/auth\", \"title\": \"Authentication\" }] }\n ]\n}\n```\n\n**Sections** (`sections.json`):\n```json\n[\n { \"label\": \"Docs\", \"slug\": \"\" },\n { \"label\": \"API Reference\", \"slug\": \"api\" }\n]\n```";
@@ -1,47 +1,69 @@
1
1
  export const platformNavigationSettingsMdxTemplate = `---
2
2
  title: "Navigation Settings"
3
- description: "Define the sidebar structure for your documentation site with categories and links."
4
- date: "2026-02-19"
3
+ description: "Define the sidebar structure for your documentation site using the drag-and-drop Navigation Builder."
4
+ date: "2026-02-24"
5
5
  category: "Configuration"
6
6
  categoryOrder: 2
7
7
  order: 2
8
8
  section: "Platform"
9
9
  ---
10
10
  # Navigation Settings
11
- The Navigation settings page lets you define your sidebar structure through a visual interface. Organize your pages into categories and control the order they appear.
11
+ The Navigation Builder lets you define your sidebar structure through a visual, drag-and-drop interface. It lives inside the **File Explorer** as a dedicated **Navigation** tab, alongside the Files and Media tabs.
12
12
 
13
13
  ## Structure
14
- Navigation is organized into **categories**, each containing one or more **links**:
14
+ Navigation is organized into three levels:
15
15
 
16
- - **Category** - a group label in the sidebar (e.g., "Getting Started", "API Reference")
17
- - **Link** - an entry within a category, defined by a page slug and display title
16
+ - **Section** - a top-level area of your site (e.g., "Docs", "API Reference"). Each section appears as a tab below the site header and has its own sidebar. Sections are defined by a label, URL slug, and docs directory.
17
+ - **Category** - a group label within a section's sidebar (e.g., "Getting Started")
18
+ - **Link** - an individual page entry within a category, defined by a slug and title
19
+
20
+ ## Drag-and-drop reordering
21
+ You can reorder items at every level by dragging their handles:
22
+
23
+ - **Drag categories** between sections or within the same section to change their position
24
+ - **Drag links** between categories or within the same category to reorder them
25
+
26
+ ## Managing sections
27
+ - Click **Add Section** in the toolbar to create a new section. Fill in the label, slug, and directory fields.
28
+ - Click the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory.
29
+ - Delete a section from its edit modal using the delete button. The root section cannot be deleted.
30
+
31
+ ### The default section
32
+ One section should have an empty slug (\`""\`). This is the default/root section that serves pages at the root URL. Pages not assigned to any other section belong here.
33
+
34
+ ### Frontmatter-based sections
35
+ You can also define sections purely through page frontmatter without using the Navigation Builder. Add a \`section\` field to your MDX frontmatter and Doccupine will create sections automatically. See the [Sections documentation](/sections) for details.
18
36
 
19
37
  ## Managing categories
20
- - Click **Add Category** to create a new group
21
- - Use the **up/down arrows** to reorder categories
22
- - Click the **remove** button to delete a category and its links
23
-
24
- ## Managing links
25
- Within each category:
26
- - Click **Add Link** to add a new page entry
27
- - Set the **slug** to the page's URL path (e.g., \`getting-started\`)
28
- - Set the **title** to the display text shown in the sidebar
29
- - Click the **remove** button to delete a link
30
-
31
- ## Per-section navigation
32
- If your site uses [sections](/sections), the Navigation settings page shows a tab bar for each section. You can configure independent sidebar structures for each section.
33
-
34
- <Callout type="note">
35
- Sections without a custom navigation configuration fall back to auto-generated navigation based on page frontmatter (\`category\`, \`categoryOrder\`, and \`order\` fields).
38
+ - Click **Add Category** within a section to create a new group
39
+ - Click the **edit** button on a category to open its edit modal, where you can rename it or delete it
40
+
41
+ ## Adding links
42
+ Within each category, click **Add files** to open a popover that lists available MDX files not yet included in the navigation. You can:
43
+
44
+ - **Search** by file name to filter the list
45
+ - **Select** one or more files using checkboxes
46
+ - Click **Add** to insert them as links in the category
47
+
48
+ To remove a link, click the **delete** button next to it.
49
+
50
+ ## Regenerate from files
51
+ The toolbar includes a **Regenerate** button that rebuilds the entire navigation tree from your MDX files' frontmatter (\`category\`, \`categoryOrder\`, and \`order\` fields). A confirmation modal appears before any existing manual navigation is replaced.
52
+
53
+ <Callout type="warning">
54
+ Regenerating from files replaces your current navigation structure. This cannot be undone.
36
55
  </Callout>
37
56
 
38
57
  ## Auto-generated vs. manual
39
- If you don't configure navigation at all, Doccupine automatically builds your sidebar from page frontmatter. The Navigation settings page is only needed when you want explicit control over the order and grouping.
58
+ If you don't configure navigation at all, Doccupine automatically builds your sidebar from page frontmatter. The Navigation Builder is only needed when you want explicit control over the order and grouping.
40
59
 
41
60
  ## How it works
42
- Navigation settings are stored in \`navigation.json\` at the root of your repository. The file supports two formats:
61
+ When you save, the Navigation Builder writes two files to your repository as pending changes:
43
62
 
44
- **Array format** for single-section sites:
63
+ - \`navigation.json\` - the category and link structure for each section
64
+ - \`sections.json\` - the list of sections with their labels, slugs, and directories
65
+
66
+ **Array format** for single-section sites (\`navigation.json\`):
45
67
  \`\`\`json
46
68
  [
47
69
  {
@@ -53,7 +75,7 @@ Navigation settings are stored in \`navigation.json\` at the root of your reposi
53
75
  ]
54
76
  \`\`\`
55
77
 
56
- **Object format** for multi-section sites:
78
+ **Object format** for multi-section sites (\`navigation.json\`):
57
79
  \`\`\`json
58
80
  {
59
81
  "": [
@@ -63,4 +85,12 @@ Navigation settings are stored in \`navigation.json\` at the root of your reposi
63
85
  { "label": "Auth", "links": [{ "slug": "api/auth", "title": "Authentication" }] }
64
86
  ]
65
87
  }
88
+ \`\`\`
89
+
90
+ **Sections** (\`sections.json\`):
91
+ \`\`\`json
92
+ [
93
+ { "label": "Docs", "slug": "" },
94
+ { "label": "API Reference", "slug": "api" }
95
+ ]
66
96
  \`\`\``;
@@ -1 +1 @@
1
- export declare const platformSectionsSettingsMdxTemplate = "---\ntitle: \"Sections Settings\"\ndescription: \"Split your documentation into top-level areas with independent sidebars using the visual section manager.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 2\norder: 3\nsection: \"Platform\"\n---\n# Sections Settings\nThe Sections settings page lets you divide your documentation into separate top-level areas. Each section appears as a tab below the site header and has its own sidebar navigation.\n\n## Adding a section\nClick **Add Section** and fill in the fields:\n\n- **Label** - the display name shown in the tab bar (e.g., \"API Reference\")\n- **Slug** - the URL prefix for pages in this section (e.g., `api`). Use an empty string for the default/root section.\n- **Directory** (optional) - the subdirectory containing this section's files. Only needed when the directory name differs from the slug.\n\n## Reordering sections\nUse the **up/down arrows** to change the order of sections in the tab bar. The first section in the list appears on the left.\n\n## The default section\nOne section should have an empty slug (`\"\"`). This is the default section that serves pages at the root URL. Pages not assigned to any other section belong here.\n\n<Callout type=\"note\">\n For a deeper explanation of how sections work, including frontmatter-based sections and URL routing, see the [Sections documentation](/sections).\n</Callout>\n\n## How it works\nSection settings are stored in `sections.json` at the root of your repository:\n\n```json\n[\n { \"label\": \"Docs\", \"slug\": \"\" },\n { \"label\": \"API Reference\", \"slug\": \"api\" },\n { \"label\": \"SDKs\", \"slug\": \"sdks\" }\n]\n```\n\nYou can also define sections purely through page frontmatter without using this settings page. See the [Sections documentation](/sections) for details on both approaches.";
1
+ export declare const platformSectionsSettingsMdxTemplate = "---\ntitle: \"Sections Settings\"\ndescription: \"Split your documentation into top-level areas with independent sidebars using the Navigation Builder.\"\ndate: \"2026-02-24\"\ncategory: \"Configuration\"\ncategoryOrder: 2\norder: 3\nsection: \"Platform\"\n---\n# Sections Settings\nSections let you divide your documentation into separate top-level areas. Each section appears as a tab below the site header and has its own sidebar navigation.\n\nSections are managed inside the [Navigation Builder](/navigation-settings), which is available as the **Navigation** tab in the File Explorer.\n\n## Adding a section\nIn the Navigation Builder toolbar, click **Add Section** and fill in the fields:\n\n- **Label** - the display name shown in the tab bar (e.g., \"API Reference\")\n- **Slug** - the URL prefix for pages in this section (e.g., `api`). Use an empty string for the default/root section.\n- **Directory** - the subdirectory containing this section's files. Only needed when the directory name differs from the slug.\n\n## Editing and deleting sections\nClick the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory. The delete button is available inside the modal for non-root sections.\n\n## Reordering sections\nDrag sections by their handle to reorder them. The first section in the list appears on the left in the tab bar.\n\n## The default section\nOne section should have an empty slug (`\"\"`). This is the default section that serves pages at the root URL. Pages not assigned to any other section belong here.\n\n<Callout type=\"note\">\n For a deeper explanation of how sections work, including frontmatter-based sections and URL routing, see the [Sections documentation](/sections).\n</Callout>\n\n## How it works\nSection settings are stored in `sections.json` at the root of your repository:\n\n```json\n[\n { \"label\": \"Docs\", \"slug\": \"\" },\n { \"label\": \"API Reference\", \"slug\": \"api\" },\n { \"label\": \"SDKs\", \"slug\": \"sdks\" }\n]\n```\n\nYou can also define sections purely through page frontmatter without using the Navigation Builder. See the [Sections documentation](/sections) for details on both approaches.";
@@ -1,24 +1,29 @@
1
1
  export const platformSectionsSettingsMdxTemplate = `---
2
2
  title: "Sections Settings"
3
- description: "Split your documentation into top-level areas with independent sidebars using the visual section manager."
4
- date: "2026-02-19"
3
+ description: "Split your documentation into top-level areas with independent sidebars using the Navigation Builder."
4
+ date: "2026-02-24"
5
5
  category: "Configuration"
6
6
  categoryOrder: 2
7
7
  order: 3
8
8
  section: "Platform"
9
9
  ---
10
10
  # Sections Settings
11
- The Sections settings page lets you divide your documentation into separate top-level areas. Each section appears as a tab below the site header and has its own sidebar navigation.
11
+ Sections let you divide your documentation into separate top-level areas. Each section appears as a tab below the site header and has its own sidebar navigation.
12
+
13
+ Sections are managed inside the [Navigation Builder](/navigation-settings), which is available as the **Navigation** tab in the File Explorer.
12
14
 
13
15
  ## Adding a section
14
- Click **Add Section** and fill in the fields:
16
+ In the Navigation Builder toolbar, click **Add Section** and fill in the fields:
15
17
 
16
18
  - **Label** - the display name shown in the tab bar (e.g., "API Reference")
17
19
  - **Slug** - the URL prefix for pages in this section (e.g., \`api\`). Use an empty string for the default/root section.
18
- - **Directory** (optional) - the subdirectory containing this section's files. Only needed when the directory name differs from the slug.
20
+ - **Directory** - the subdirectory containing this section's files. Only needed when the directory name differs from the slug.
21
+
22
+ ## Editing and deleting sections
23
+ Click the **edit** button on a section header to open its edit modal, where you can update the label, slug, and directory. The delete button is available inside the modal for non-root sections.
19
24
 
20
25
  ## Reordering sections
21
- Use the **up/down arrows** to change the order of sections in the tab bar. The first section in the list appears on the left.
26
+ Drag sections by their handle to reorder them. The first section in the list appears on the left in the tab bar.
22
27
 
23
28
  ## The default section
24
29
  One section should have an empty slug (\`""\`). This is the default section that serves pages at the root URL. Pages not assigned to any other section belong here.
@@ -38,4 +43,4 @@ Section settings are stored in \`sections.json\` at the root of your repository:
38
43
  ]
39
44
  \`\`\`
40
45
 
41
- You can also define sections purely through page frontmatter without using this settings page. See the [Sections documentation](/sections) for details on both approaches.`;
46
+ You can also define sections purely through page frontmatter without using the Navigation Builder. See the [Sections documentation](/sections) for details on both approaches.`;
@@ -1 +1 @@
1
- export declare const orderNavItemsTemplate = "export interface PagesProps {\n slug: string;\n title: string;\n date: string;\n category: string;\n description?: string;\n path?: string;\n categoryOrder?: number;\n order?: number;\n section?: string;\n}\n\ninterface AccProps {\n [key: string]: {\n categoryOrder: number;\n pages: { date: string; slug: string; title: string; order: number }[];\n };\n}\n\nfunction transformPagesToGroupedStructure(pages: PagesProps[]) {\n const grouped = pages.reduce((acc: AccProps, page: PagesProps) => {\n const category = page.category || \"Uncategorized\";\n\n if (!acc[category]) {\n acc[category] = {\n categoryOrder: page.categoryOrder || 0,\n pages: [],\n };\n }\n\n acc[category].pages.push({\n date: page.date,\n slug: page.slug,\n title: page.title,\n order: page.order || 0,\n });\n\n return acc;\n }, {});\n\n return Object.entries(grouped)\n .sort(([, a], [, b]) => a.categoryOrder - b.categoryOrder)\n .map(([categoryName, categoryData], index) => ({\n slug: index === 0 ? \"\" : categoryName.toLowerCase().replace(/s+/g, \"-\"),\n label: categoryName,\n links: categoryData.pages.sort((a, b) => a.order - b.order),\n }));\n}\n\nexport { transformPagesToGroupedStructure };\n";
1
+ export declare const orderNavItemsTemplate = "export interface PagesProps {\n slug: string;\n title: string;\n date: string | null;\n category: string;\n description?: string;\n path?: string;\n categoryOrder?: number;\n order?: number;\n section?: string;\n}\n\ninterface AccProps {\n [key: string]: {\n categoryOrder: number;\n pages: {\n date: string | null;\n slug: string;\n title: string;\n order: number;\n }[];\n };\n}\n\nfunction transformPagesToGroupedStructure(pages: PagesProps[]) {\n const grouped = pages.reduce((acc: AccProps, page: PagesProps) => {\n const category = page.category || \"Uncategorized\";\n\n if (!acc[category]) {\n acc[category] = {\n categoryOrder: page.categoryOrder || 0,\n pages: [],\n };\n }\n\n acc[category].pages.push({\n date: page.date,\n slug: page.slug,\n title: page.title,\n order: page.order || 0,\n });\n\n return acc;\n }, {});\n\n return Object.entries(grouped)\n .sort(([, a], [, b]) => a.categoryOrder - b.categoryOrder)\n .map(([categoryName, categoryData], index) => ({\n slug: index === 0 ? \"\" : categoryName.toLowerCase().replace(/s+/g, \"-\"),\n label: categoryName,\n links: categoryData.pages.sort((a, b) => a.order - b.order),\n }));\n}\n\nexport { transformPagesToGroupedStructure };\n";
@@ -1,7 +1,7 @@
1
1
  export const orderNavItemsTemplate = `export interface PagesProps {
2
2
  slug: string;
3
3
  title: string;
4
- date: string;
4
+ date: string | null;
5
5
  category: string;
6
6
  description?: string;
7
7
  path?: string;
@@ -13,7 +13,12 @@ export const orderNavItemsTemplate = `export interface PagesProps {
13
13
  interface AccProps {
14
14
  [key: string]: {
15
15
  categoryOrder: number;
16
- pages: { date: string; slug: string; title: string; order: number }[];
16
+ pages: {
17
+ date: string | null;
18
+ slug: string;
19
+ title: string;
20
+ order: number;
21
+ }[];
17
22
  };
18
23
  }
19
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.67",
3
+ "version": "0.0.69",
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": {