feat: fumadocs - v4#24
Conversation
WalkthroughMigrates the docs site from Docusaurus to a React Router + Vite + Fumadocs stack: adds an app shell, routes, layout/UI primitives, MDX+Mermaid support, many new components/utilities, and extensive MDX content; removes legacy Docusaurus configs, pages, and CSS; updates CI, tooling, and editor ignores/settings. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Router as React Router
participant Loader as Docs Loader
participant Source as Fumadocs Source
participant Renderer as MDX Renderer
participant Layout as DocsLayout / DocsPage
User->>Router: navigate to /docs/:slug
Router->>Loader: call loader({ params })
Loader->>Source: source.getPage(slug)
Source-->>Loader: page + tree or 404
Loader-->>Router: loader data
Router->>Layout: render DocsLayout(tree, options)
Layout->>Renderer: render MDX with getMDXComponents (includes Mermaid)
Renderer-->>User: deliver rendered HTML/JS
sequenceDiagram
autonumber
actor User
participant Toggle as SearchToggle
participant Dialog as DefaultSearchDialog
participant Hook as useSearchHook
participant Index as Orama Index
User->>Toggle: click search button
Toggle->>Dialog: setOpen(true)
Dialog->>Hook: query(term, locale)
Hook->>Index: run search
Index-->>Hook: results
Hook-->>Dialog: data / loading
Dialog-->>User: render results
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 30
🧹 Nitpick comments (23)
content/docs/reference/headers.mdx (1)
29-30: Fix broken link target.The link still points to
/docs/guides/access-controls.md#access-controls-using-oidc-groups. In the new MDX routing the.mdextension resolves to a 404 (you already use the extensionless form in the label-discovery callout). Drop the.mdhere so it matches the new path and stays consistent.content/docs/guides/totp.mdx (1)
2-2: Hyphenate the title for readability.Doc style elsewhere treats “two-factor” as a hyphenated compound, so tweak the title to match.
-title: Two factor authentication +title: Two-factor authenticationcontent/docs/community/caddy.mdx (1)
22-28: Usecaddyfilesyntax highlighting for the snippet. Syntax highlighting is currently set to TypeScript, which miscolors the example. Switching tocaddyfile(orcaddy) keeps the snippet accurate and easier to read. Based on learningscontent/docs/guides/runtipi.mdx (1)
18-22: Consider using consistent image syntax.The guide uses both HTML
<img>tags (lines 18-22) and Markdown image syntax (line 30). For consistency and to leverage any MDX image optimization features, consider using a single approach throughout.app/lib/merge-refs.ts (1)
10-11: Add type guard for RefObject.The current check
else if (ref)does not verify thatrefis a mutableRefObjectwith acurrentproperty. While TypeScript'sReact.Ref<T>union type should prevent invalid refs, adding an explicit check improves runtime safety.Apply this diff:
if (typeof ref === 'function') { ref(value); - } else if (ref) { + } else if (ref && 'current' in ref) { ref.current = value; }app/lib/is-active.ts (1)
3-6: Normalize search/hash tooPaths with
?query/#hashwon’t match. Strip them in normalize to harden comparisons.Apply:
function normalize(url: string) { - if (url.length > 1 && url.endsWith('/')) return url.slice(0, -1); - return url; + const [path] = url.split(/[?#]/, 1); + if (path.length > 1 && path.endsWith('/')) return path.slice(0, -1); + return path; } export function isActive( url: string, pathname: string, nested = true, ): boolean { url = normalize(url); pathname = normalize(pathname); return url === pathname || (nested && pathname.startsWith(`${url}/`)); }Also applies to: 13-17
app/root.tsx (1)
34-38: Optional: add SRI/CSP for analytics scriptConsider Subresource Integrity and/or a nonce-compatible setup if you enforce CSP.
package.json (1)
1-45: Declare Node engine (ESM + Fumadocs require modern Node)Add engines to avoid unexpected CI/runtime issues.
"devDependencies": { "@react-router/dev": "^7.8.2", "@tailwindcss/vite": "^4.1.12", "@types/mdx": "^2.0.13", "@types/node": "^24.3.0", "@types/react": "^19.1.12", "@types/react-dom": "^19.1.9", "react-router-devtools": "^5.1.3", "tailwindcss": "^4.1.12", "typescript": "^5.9.2", "vite": "^7.1.3", "vite-tsconfig-paths": "^5.1.4" } + , + "engines": { + "node": ">=20" + }Based on learnings
app/components/mdx/mermaid.tsx (1)
47-50: Cache key sizeUsing the full chart text as part of the cache key may grow memory. Consider hashing the chart string (e.g., stable SHA-1) for the key.
app/components/navigation-menu.tsx (1)
56-69: Align forwarded ref type with actual elementYou forward the ref to the wrapper div but type it as Viewport’s ref. Either forward the ref to Primitive.Viewport or change the ref type to HTMLDivElement. Here’s the minimal typing fix.
-const NavigationMenuViewport = React.forwardRef< - React.ComponentRef<typeof Primitive.Viewport>, - React.ComponentPropsWithoutRef<typeof Primitive.Viewport> +const NavigationMenuViewport = React.forwardRef< + HTMLDivElement, + React.ComponentPropsWithoutRef<typeof Primitive.Viewport> >(({ className, ...props }, ref) => ( <div ref={ref} className="flex w-full justify-center"> <Primitive.Viewport {...props} className={cn( 'relative h-(--radix-navigation-menu-viewport-height) w-full origin-[top_center] overflow-hidden transition-[width,height] duration-300 data-[state=closed]:animate-fd-nav-menu-out data-[state=open]:animate-fd-nav-menu-in', className, )} /> </div> ));app/components/sidebar.tsx (1)
4-14: Clear pending timeout on unmount to avoid setState after unmountA hovering timeout may fire after unmount. Add a cleanup and import useEffect.
import { type ComponentProps, createContext, type FC, Fragment, type ReactNode, useContext, + useEffect, useMemo, useRef, useState, } from "react"; @@ useOnChange(collapsed, () => { setHover(false); closeTimeRef.current = Date.now() + 150; }); + useEffect(() => { + return () => { + window.clearTimeout(timerRef.current); + }; + }, []);Also applies to: 111-121
app/components/github.tsx (1)
3-18: Improve SVG accessibility (optional)If decorative, mark as aria-hidden and non-focusable. If meaningful, pass an aria-label or title.
Example:
- <svg aria-hidden="true" focusable="false" ... />
- or <svg aria-label="GitHub" role="img" ... />
app/docs/search.ts (1)
5-8: Consider making language configurable.The search language is hardcoded to
'english'. If you plan to support internationalization in the future, consider extracting this to a configuration file or environment variable.+import { searchConfig } from '@/config/search'; + const server = createFromSource(source, { - language: 'english', + language: searchConfig.language ?? 'english', });app/components/ui/button.tsx (1)
3-9: Consider extracting variants to a shared theme constant.The variants object could be extracted to a centralized theme configuration if you plan to reuse these color schemes across other components (e.g., badges, cards).
+// In app/lib/theme.ts +export const colorVariants = { + primary: 'bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80', + // ... +} as const; +// In button.tsx +import { colorVariants } from '@/lib/theme'; + -const variants = { - primary: 'bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80', - // ... -} as const;source.config.ts (1)
4-6: Consider adding additional collection metadata.While the minimal configuration is valid, you might want to add schema validation, frontmatter types, or collection metadata for better type safety and documentation structure.
export const docs = defineDocs({ dir: "content/docs", + schema: { + frontmatter: z.object({ + title: z.string(), + description: z.string().optional(), + }), + }, });app/components/card.tsx (2)
14-23: Consider adding accessibility attributes.The card div should have appropriate ARIA attributes if it's interactive or represents a distinct content region. Consider adding
role="article"or wrapping in a semantic<article>tag.return ( - <div className="flex flex-col gap-4 p-4 shadow-xs border border-fd-border rounded-md hover:border-fd-accent transition-colors max-w-xs"> + <article className="flex flex-col gap-4 p-4 shadow-xs border border-fd-border rounded-md hover:border-fd-accent transition-colors max-w-xs"> {icon} <div className="flex flex-col gap-2"> - <p className="text-md font-semibold">{title}</p> + <h3 className="text-md font-semibold">{title}</h3> <p className="text-sm">{description}</p> </div> {children} - </div> + </article> );
15-15: Consider using a variant system for card styles.Similar to the button component, you could introduce variants for different card sizes, styles, or hover effects using CVA for consistency across your UI components.
import { cva } from 'class-variance-authority'; const cardVariants = cva( 'flex flex-col gap-4 p-4 shadow-xs border rounded-md transition-colors', { variants: { size: { sm: 'max-w-xs', md: 'max-w-md', lg: 'max-w-lg', }, variant: { default: 'border-fd-border hover:border-fd-accent', featured: 'border-fd-primary hover:border-fd-primary/80', }, }, defaultVariants: { size: 'sm', variant: 'default', }, }, );app/components/ui/collapsible.tsx (1)
14-18: Mounted state prevents FOUC but adds re-render.The mounted state pattern defers animation classes until after hydration, preventing flash-of-unstyled-content. However, this forces a re-render on every mount.
If animations are purely decorative and the FOUC is acceptable, you could remove the mounted state:
- const [mounted, setMounted] = useState(false); - - useEffect(() => { - setMounted(true); - }, []); - return ( <CollapsiblePrimitive.CollapsibleContent ref={ref} {...props} className={cn( 'overflow-hidden', - mounted && - 'data-[state=closed]:animate-fd-collapsible-up data-[state=open]:animate-fd-collapsible-down', + 'data-[state=closed]:animate-fd-collapsible-up data-[state=open]:animate-fd-collapsible-down', props.className, )} >Keep the current implementation if avoiding FOUC is a priority.
app/components/language-toggle.tsx (1)
15-16: Error message could be more helpful.The error thrown when locales are missing is generic. Consider providing guidance on how to fix the issue.
Apply this diff:
- if (!context.locales) throw new Error('Missing `<I18nProvider />`'); + if (!context.locales) { + throw new Error( + 'Missing locales in I18nProvider. Ensure <I18nProvider> wraps this component and includes a locales prop.' + ); + }app/components/layout/docs/client.tsx (1)
58-66: Consider extracting magic numbers to CSS variables.The hardcoded spacing calculation on line 64 could be more maintainable as a named CSS variable.
Define a CSS variable in your theme:
:root { --fd-collapsible-control-offset: calc(var(--fd-banner-height) + var(--fd-tocnav-height) + var(--spacing) * 4); }Then use it:
style={{ - top: 'calc(var(--fd-banner-height) + var(--fd-tocnav-height) + var(--spacing) * 4)', + top: 'var(--fd-collapsible-control-offset)', }}app/components/search-toggle.tsx (1)
71-75: Use stable keys for hotKey itemsAvoid array index as key; prefer a stable id (e.g., k.code or k.display).
- {hotKey.map((k, i) => ( - <kbd key={i} className="rounded-md border bg-fd-background px-1.5"> + {hotKey.map((k) => ( + <kbd key={k.display} className="rounded-md border bg-fd-background px-1.5"> {k.display} </kbd> ))}app/components/layout/home/navbar.tsx (1)
27-54: Type accuracy: Navbar renders a header but props typed as divUse ComponentProps<'header'> for correctness and consistency (matches other Navbar usage in docs layout).
-export function Navbar(props: ComponentProps<"div">) { +export function Navbar(props: ComponentProps<"header">) {app/components/layout/docs/page-client.tsx (1)
59-63: ProgressCircle ARIA: use 0–100 scale for better SR outputExpose progress as percentage (0–100) and keep default max=100. Screen readers announce percentages more naturally.
- <ProgressCircle - value={(selected + 1) / Math.max(1, items.length)} - max={1} + <ProgressCircle + value={((selected + 1) / Math.max(1, items.length)) * 100} className={cn('shrink-0', open && 'text-fd-primary')} />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (29)
bun.lockis excluded by!**/*.lockpublic/favicon.icois excluded by!**/*.icopublic/icon/logo.pngis excluded by!**/*.pngpublic/screenshots/github-app-client-secret.pngis excluded by!**/*.pngpublic/screenshots/github-app-email.pngis excluded by!**/*.pngpublic/screenshots/github-app-home.pngis excluded by!**/*.pngpublic/screenshots/github-app-new.pngis excluded by!**/*.pngpublic/screenshots/github-new-oauth-app.pngis excluded by!**/*.pngpublic/screenshots/github-oauth-app-homepage.pngis excluded by!**/*.pngpublic/screenshots/github-oauth-client-secret.pngis excluded by!**/*.pngpublic/screenshots/google-cloud-home.pngis excluded by!**/*.pngpublic/screenshots/google-cloud-oauth-configure.pngis excluded by!**/*.pngpublic/screenshots/google-cloud-oauth-created.pngis excluded by!**/*.pngpublic/screenshots/google-cloud-oauth-home.pngis excluded by!**/*.pngpublic/screenshots/lldap-create-user.pngis excluded by!**/*.pngpublic/screenshots/lldap-groups.pngis excluded by!**/*.pngpublic/screenshots/npm-nginx.pngis excluded by!**/*.pngpublic/screenshots/npm-tinyauth.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-client-page.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-group-home.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-home.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-new-client.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-new-group.pngis excluded by!**/*.pngpublic/screenshots/runtipi-enable-advanced-settings.pngis excluded by!**/*.pngpublic/screenshots/runtipi-forward-auth-url.pngis excluded by!**/*.pngpublic/screenshots/runtipi-install-app-example.pngis excluded by!**/*.pngpublic/screenshots/tinyauth-dark.pngis excluded by!**/*.pngpublic/screenshots/zitadel-configuration-view.pngis excluded by!**/*.pngpublic/screenshots/zitadel-redirect-view.pngis excluded by!**/*.png
📒 Files selected for processing (101)
.gitignore(1 hunks).prettierignore(1 hunks).vscode/settings.json(1 hunks)app/app.css(1 hunks)app/cli.json(1 hunks)app/components/card.tsx(1 hunks)app/components/discord.tsx(1 hunks)app/components/github.tsx(1 hunks)app/components/language-toggle.tsx(1 hunks)app/components/layout/docs/client.tsx(1 hunks)app/components/layout/docs/index.tsx(1 hunks)app/components/layout/docs/page-client.tsx(1 hunks)app/components/layout/docs/page.tsx(1 hunks)app/components/layout/home/index.tsx(1 hunks)app/components/layout/home/menu.tsx(1 hunks)app/components/layout/home/navbar.tsx(1 hunks)app/components/layout/page.tsx(1 hunks)app/components/layout/shared/client.tsx(1 hunks)app/components/layout/shared/index.tsx(1 hunks)app/components/mdx-components.tsx(1 hunks)app/components/mdx/mermaid.tsx(1 hunks)app/components/navigation-menu.tsx(1 hunks)app/components/root-toggle.tsx(1 hunks)app/components/search-toggle.tsx(1 hunks)app/components/sidebar.tsx(1 hunks)app/components/theme-toggle.tsx(1 hunks)app/components/ui/button.tsx(1 hunks)app/components/ui/collapsible.tsx(1 hunks)app/components/ui/popover.tsx(1 hunks)app/components/ui/scroll-area.tsx(1 hunks)app/components/ui/toc-clerk.tsx(1 hunks)app/components/ui/toc-thumb.tsx(1 hunks)app/components/ui/toc.tsx(1 hunks)app/docs/page.tsx(1 hunks)app/docs/search.ts(1 hunks)app/lib/cn.ts(1 hunks)app/lib/is-active.ts(1 hunks)app/lib/layout.shared.tsx(1 hunks)app/lib/merge-refs.ts(1 hunks)app/lib/source.ts(1 hunks)app/root.tsx(1 hunks)app/routes.ts(1 hunks)app/routes/home.tsx(1 hunks)content/docs/about.mdx(1 hunks)content/docs/changelog.mdx(2 hunks)content/docs/community/caddy.mdx(1 hunks)content/docs/community/kubernetes.mdx(6 hunks)content/docs/community/zitadel-oauth.mdx(1 hunks)content/docs/contributing.mdx(1 hunks)content/docs/getting-started.mdx(1 hunks)content/docs/guides/access-controls.mdx(1 hunks)content/docs/guides/advanced.mdx(1 hunks)content/docs/guides/github-app-oauth.mdx(1 hunks)content/docs/guides/github-oauth.mdx(1 hunks)content/docs/guides/google-oauth.mdx(1 hunks)content/docs/guides/ldap.mdx(1 hunks)content/docs/guides/nginx-proxy-manager.mdx(1 hunks)content/docs/guides/pocket-id.mdx(1 hunks)content/docs/guides/runtipi.mdx(1 hunks)content/docs/guides/totp.mdx(1 hunks)content/docs/guides/using-the-binary.mdx(1 hunks)content/docs/meta.json(1 hunks)content/docs/reference/cli.mdx(1 hunks)content/docs/reference/configuration.mdx(1 hunks)content/docs/reference/flow.mdx(1 hunks)content/docs/reference/headers.mdx(1 hunks)docs/about.md(0 hunks)docs/community/_category_.yml(0 hunks)docs/community/caddy.md(0 hunks)docs/community/zitadel-oauth.md(0 hunks)docs/contributing.md(0 hunks)docs/getting-started.md(0 hunks)docs/guides/_category_.yml(0 hunks)docs/guides/access-controls.md(0 hunks)docs/guides/advanced.md(0 hunks)docs/guides/github-app-oauth.md(0 hunks)docs/guides/github-oauth.md(0 hunks)docs/guides/google-oauth.md(0 hunks)docs/guides/ldap.md(0 hunks)docs/guides/nginx-proxy-manager.md(0 hunks)docs/guides/pocket-id.md(0 hunks)docs/guides/runtipi.md(0 hunks)docs/guides/totp.md(0 hunks)docs/guides/using-the-binary.md(0 hunks)docs/reference/_category_.yml(0 hunks)docs/reference/api.md(0 hunks)docs/reference/cli.md(0 hunks)docs/reference/configuration.md(0 hunks)docs/reference/flow.md(0 hunks)docs/reference/headers.md(0 hunks)docusaurus.config.ts(0 hunks)package.json(1 hunks)react-router.config.ts(1 hunks)sidebars.ts(0 hunks)source.config.ts(1 hunks)source.generated.ts(1 hunks)src/css/custom.css(0 hunks)src/pages/index.module.css(0 hunks)src/pages/index.tsx(0 hunks)tsconfig.json(1 hunks)vite.config.ts(1 hunks)
💤 Files with no reviewable changes (29)
- docs/reference/api.md
- docs/guides/ldap.md
- docs/reference/flow.md
- docs/about.md
- docs/guides/google-oauth.md
- docs/contributing.md
- docs/guides/advanced.md
- src/pages/index.tsx
- docs/guides/using-the-binary.md
- docs/guides/access-controls.md
- docusaurus.config.ts
- src/pages/index.module.css
- docs/community/category.yml
- docs/community/zitadel-oauth.md
- docs/guides/totp.md
- docs/guides/category.yml
- docs/guides/nginx-proxy-manager.md
- docs/reference/category.yml
- sidebars.ts
- docs/guides/pocket-id.md
- docs/guides/github-oauth.md
- docs/guides/runtipi.md
- docs/reference/cli.md
- docs/reference/configuration.md
- docs/reference/headers.md
- src/css/custom.css
- docs/community/caddy.md
- docs/guides/github-app-oauth.md
- docs/getting-started.md
🧰 Additional context used
🧬 Code graph analysis (26)
app/lib/layout.shared.tsx (1)
app/components/layout/shared/index.tsx (1)
BaseLayoutProps(20-61)
app/components/root-toggle.tsx (1)
app/lib/is-active.ts (2)
isTabActive(19-23)isActive(8-17)
app/components/ui/toc-clerk.tsx (3)
app/components/ui/toc.tsx (1)
useTOCItems(12-14)app/components/ui/toc-thumb.tsx (1)
TocThumb(38-73)app/lib/merge-refs.ts (1)
mergeRefs(3-15)
app/components/search-toggle.tsx (1)
app/components/ui/button.tsx (2)
ButtonProps(28-28)buttonVariants(11-26)
app/routes/home.tsx (5)
app/components/layout/home/index.tsx (1)
HomeLayout(40-76)app/lib/layout.shared.tsx (1)
baseOptions(3-9)app/components/card.tsx (1)
Card(8-24)app/components/github.tsx (1)
MdiGithub(3-18)app/components/discord.tsx (1)
IcBaselineDiscord(3-18)
content/docs/about.mdx (1)
src/pages/index.tsx (1)
Home(113-122)
app/components/layout/home/menu.tsx (3)
app/components/ui/button.tsx (1)
buttonVariants(11-26)app/components/layout/shared/index.tsx (2)
LinkItemType(146-151)BaseLinkItem(182-182)app/components/layout/shared/client.tsx (1)
BaseLinkItem(8-30)
app/docs/search.ts (2)
app/lib/source.ts (1)
source(4-7)app/docs/page.tsx (1)
loader(16-25)
source.config.ts (1)
source.generated.ts (1)
docs(7-21)
source.generated.ts (1)
source.config.ts (1)
docs(4-6)
app/components/layout/shared/index.tsx (1)
app/components/layout/docs/index.tsx (1)
LinkItemType(385-385)
app/components/card.tsx (1)
src/pages/index.tsx (2)
Header(9-111)Home(113-122)
app/components/layout/home/index.tsx (8)
app/components/layout/shared/index.tsx (4)
BaseLayoutProps(20-61)NavOptions(5-18)getLinks(156-180)LinkItemType(146-151)app/components/layout/home/navbar.tsx (6)
Navbar(27-54)NavbarMenuLink(83-97)NavbarMenu(56-56)NavbarMenuTrigger(72-81)NavbarMenuContent(58-70)NavbarLink(118-136)app/components/search-toggle.tsx (2)
LargeSearchToggle(45-79)SearchToggle(15-43)app/components/theme-toggle.tsx (1)
ThemeToggle(27-88)app/components/github.tsx (1)
MdiGithub(3-18)app/components/language-toggle.tsx (2)
LanguageToggle(14-57)LanguageToggleText(59-68)app/components/layout/home/menu.tsx (4)
Menu(89-89)MenuTrigger(91-108)MenuContent(110-121)MenuLinkItem(34-87)app/components/ui/button.tsx (1)
buttonVariants(11-26)
app/lib/source.ts (3)
app/docs/page.tsx (1)
loader(16-25)source.generated.ts (2)
create(5-5)docs(7-21)source.config.ts (1)
docs(4-6)
app/components/layout/home/navbar.tsx (3)
app/components/layout/docs/client.tsx (1)
Navbar(12-28)app/components/ui/button.tsx (1)
buttonVariants(11-26)app/components/layout/shared/client.tsx (1)
BaseLinkItem(8-30)
app/components/layout/shared/client.tsx (2)
app/components/layout/shared/index.tsx (1)
BaseLinkType(72-81)app/lib/is-active.ts (1)
isActive(8-17)
app/root.tsx (1)
app/routes/home.tsx (1)
meta(9-17)
app/docs/page.tsx (5)
app/lib/source.ts (1)
source(4-7)source.generated.ts (1)
docs(7-21)app/components/layout/page.tsx (4)
DocsPage(113-203)DocsTitle(270-282)DocsDescription(250-266)DocsBody(233-246)app/components/mdx-components.tsx (1)
getMDXComponents(5-11)app/lib/layout.shared.tsx (1)
baseOptions(3-9)
app/components/layout/docs/client.tsx (3)
app/components/sidebar.tsx (1)
SidebarCollapseTrigger(425-441)app/components/ui/button.tsx (1)
buttonVariants(11-26)app/components/search-toggle.tsx (1)
SearchToggle(15-43)
app/components/sidebar.tsx (1)
app/lib/is-active.ts (1)
isActive(8-17)
app/components/layout/docs/page.tsx (2)
app/components/ui/toc.tsx (3)
TOCScrollArea(30-51)TOCItems(53-85)TOCProvider(16-28)app/components/ui/toc-clerk.tsx (1)
ClerkTOCItems(11-117)
app/components/layout/page.tsx (3)
app/components/layout/docs/page.tsx (14)
BreadcrumbProps(102-102)FooterProps(101-101)PageRoot(75-91)PageTOCPopover(98-98)PageTOCPopoverTrigger(99-99)PageTOCPopoverContent(100-100)PageTOCPopoverItems(46-55)PageArticle(57-69)PageBreadcrumb(94-94)PageLastUpdate(96-96)PageFooter(95-95)PageTOC(97-97)PageTOCTitle(20-33)PageTOCItems(35-44)app/components/layout/docs/page-client.tsx (9)
BreadcrumbProps(340-340)FooterProps(244-252)PageTOCPopover(163-219)PageTOCPopoverTrigger(39-91)PageTOCPopoverContent(151-161)PageBreadcrumb(342-392)PageLastUpdate(221-241)PageFooter(277-310)PageTOC(394-417)app/components/ui/button.tsx (1)
buttonVariants(11-26)
app/components/language-toggle.tsx (1)
app/components/ui/button.tsx (1)
buttonVariants(11-26)
app/components/ui/toc.tsx (2)
app/lib/merge-refs.ts (1)
mergeRefs(3-15)app/components/ui/toc-thumb.tsx (1)
TocThumb(38-73)
app/components/layout/docs/index.tsx (6)
app/components/layout/shared/index.tsx (5)
BaseLayoutProps(20-61)getLinks(156-180)IconItemType(90-102)BaseLinkItem(182-182)LinkItemType(146-151)app/components/sidebar.tsx (16)
SidebarProps(37-62)SidebarComponents(457-461)SidebarViewport(237-254)SidebarPageTree(466-523)SidebarContentMobile(178-213)SidebarHeader(215-224)SidebarTrigger(408-423)SidebarFooter(226-235)SidebarContent(111-176)SidebarCollapseTrigger(425-441)Sidebar(89-109)SidebarFolder(294-315)SidebarFolderLink(337-370)SidebarFolderTrigger(317-335)SidebarFolderContent(372-406)SidebarItem(270-292)app/components/root-toggle.tsx (2)
Option(12-14)RootToggle(16-103)app/components/layout/shared/client.tsx (1)
BaseLinkItem(8-30)app/components/github.tsx (1)
MdiGithub(3-18)app/components/layout/docs/client.tsx (3)
CollapsibleControl(54-81)Navbar(12-28)LayoutBody(30-52)
app/components/layout/docs/page-client.tsx (2)
app/components/ui/toc.tsx (1)
useTOCItems(12-14)app/lib/is-active.ts (1)
isActive(8-17)
🪛 ast-grep (0.39.5)
app/components/mdx/mermaid.tsx
[warning] 56-56: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
app/routes/home.tsx
[error] 9-9: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
app/components/mdx/mermaid.tsx
[error] 57-57: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🔇 Additional comments (29)
app/components/layout/shared/client.tsx (1)
13-17: LGTM!The active state computation correctly handles the three cases ('url', 'nested-url', 'none') and properly delegates to the
isActivehelper.app/cli.json (1)
1-11: LGTM!The CLI configuration structure is valid and provides clear directory aliases for the project. The empty
baseDirandcommandsare acceptable defaults.content/docs/guides/runtipi.mdx (1)
1-66: LGTM!The documentation is comprehensive and well-structured. The callouts effectively highlight important information about Runtipi v4 migration and domain requirements.
content/docs/guides/github-app-oauth.mdx (2)
6-6: Verify internal documentation link extension.The link to the OAuth Apps guide uses a
.mdextension:/docs/guides/github-oauth.md. Ensure this matches the actual file extension and routing configuration. If the project uses.mdxfiles, this should be/docs/guides/github-oauth(without extension) or/docs/guides/github-oauth.mdx.
1-69: LGTM!The guide is well-structured with clear step-by-step instructions. The security warning about OAuth whitelisting and the tip about exclusive OAuth login are valuable additions.
vite.config.ts (1)
8-9: LGTM!The plugin configuration is well-structured. The MDX plugin is correctly positioned first to handle content transformation before other build steps.
content/docs/guides/advanced.mdx (1)
1-90: LGTM!The advanced guide is comprehensive and well-organized. The inclusion of Mermaid diagrams, practical YAML configurations, and references to related GitHub issues provides valuable context for troubleshooting complex setups.
app/lib/merge-refs.ts (1)
3-14: LGTM!The utility correctly handles both function and object refs, providing a clean way to merge multiple refs into a single callback.
content/docs/contributing.mdx (1)
1-66: LGTM!The contributing guide is well-structured and provides clear, actionable steps for setting up a development environment. The inclusion of tips about using sslip.io and creating a separate test compose file demonstrates good developer experience considerations.
content/docs/reference/cli.mdx (2)
22-25: Verify MDX Callout component availabilityEnsure
<Callout type="tip|info">is registered in your MDX components; otherwise these won’t render.If not registered, wire it in your MDX components map (app/components/mdx-components.tsx).
Also applies to: 66-68
18-20: Confirm Docker image tag
ghcr.io/steveiliop56/tinyauth:v4should exist and be public. Consider adding--pull=alwaysto ensure fresh pulls in docs.app/root.tsx (1)
28-51: Solid root layout and error boundaryStructure, providers, and error handling look good.
Also applies to: 57-92
app/lib/layout.shared.tsx (1)
3-9: LGTMSane base options; title wired via nav.
app/docs/search.ts (1)
10-12: LGTM!The search loader implementation correctly delegates to the FumaDocs server instance. The pattern matches the recommended approach from FumaDocs documentation.
app/routes.ts (1)
3-7: LGTM!The routing configuration is well-structured with clear separation between the home page, documentation pages (with wildcard for nested routes), and API endpoints. This aligns with React Router v7 best practices.
app/components/ui/button.tsx (1)
11-28: LGTM!The button variants implementation correctly uses class-variance-authority with proper TypeScript typing. The dual variant/color keys are appropriately documented for fumadocs compatibility.
Based on learnings: CVA usage follows best practices with compound variants and typed variant props.
app/components/mdx-components.tsx (1)
5-11: LGTM!The component composition follows the correct precedence order: defaults → Mermaid override → user overrides. This allows consumers to further customize any component including Mermaid if needed.
source.config.ts (1)
8-12: LGTM!The MDX configuration correctly integrates the Mermaid plugin. This aligns with the fumadocs-mdx setup documented in the learnings.
Based on learnings: fumadocs-mdx requires source.config.ts collection definitions and integrates with remark plugins.
app/lib/source.ts (2)
2-2: Import path is correct.../../source.generatedresolves tosource.generated.tsat the repository root.
4-7: No changes needed for top-level await support. tsconfig.json uses"target": "esnext"and"module": "esnext", and Vite defaults to ESM—top-level await is supported.app/components/discord.tsx (1)
1-18: Lucide-react doesn’t include brand icons
Lucide v0.544.0 explicitly excludes social/brand logos (like Discord), so retain this custom SVG or switch to a library that provides brand icons (e.g. react-icons/si or simple-icons/react).Likely an incorrect or invalid review comment.
app/routes/home.tsx (1)
19-105: LGTM! Well-structured landing page.The component follows React best practices with proper separation of concerns, accessible markup, and semantic HTML. The use of layout primitives (HomeLayout, Card) and the component composition is clean and maintainable.
app/components/ui/collapsible.tsx (1)
10-34: LGTM! Proper forwardRef implementation.The component correctly uses forwardRef with proper TypeScript typing and forwards all props to the underlying Radix primitive. The displayName is appropriately set for debugging.
app/docs/page.tsx (1)
16-25: LGTM! Clean loader implementation.The loader correctly parses the slug, fetches the page, and handles 404 cases with proper Response construction. The structure aligns well with React Router v7 patterns.
app/components/ui/toc-clerk.tsx (2)
26-70: ResizeObserver cleanup is correct.The effect properly disconnects the ResizeObserver in the cleanup function, preventing memory leaks. The dependency array includes items, ensuring updates when the TOC changes.
87-92: Inline SVG generation is safe.Using
encodeURIComponentfor the data URI properly escapes the SVG string, preventing injection. The SVG path is computed from DOM measurements and doesn't include user input.app/components/language-toggle.tsx (1)
18-56: LGTM! Clean Popover-based language selector.The component properly uses Popover primitives, highlights the active locale, and handles selection via context.onChange. The aria-label improves accessibility.
app/components/layout/docs/client.tsx (1)
44-46: Dynamic padding uses CSS custom properties correctly.The inline style calculation for
paddingInlineStartproperly uses CSS custom properties to create responsive sidebar behavior. The logical propertypaddingInlineStartcorrectly supports RTL layouts.app/components/ui/popover.tsx (1)
10-28: LGTM! Proper Radix wrapper implementation.The component correctly wraps the Radix Popover with sensible defaults (centered alignment, 4px offset, bottom side). The forwardRef pattern is properly typed, and the Portal ensures correct stacking context. The displayName aids debugging.
| const githubLink: string = "https://github.com/steveiliop56/tinyauth"; | ||
|
|
||
| export interface DocsLayoutProps extends BaseLayoutProps { | ||
| tree: PageTree.Root; | ||
|
|
||
| sidebar?: SidebarOptions; | ||
|
|
||
| /** | ||
| * Props for the `div` container | ||
| */ | ||
| containerProps?: HTMLAttributes<HTMLDivElement>; | ||
| } | ||
|
|
||
| interface SidebarOptions | ||
| extends ComponentProps<"aside">, | ||
| Pick<SidebarProps, "defaultOpenLevel" | "prefetch"> { | ||
| enabled?: boolean; | ||
| component?: ReactNode; | ||
| components?: Partial<SidebarComponents>; | ||
|
|
||
| /** | ||
| * Root Toggle options | ||
| */ | ||
| tabs?: Option[] | GetSidebarTabsOptions | false; | ||
|
|
||
| banner?: ReactNode; | ||
| footer?: ReactNode; | ||
|
|
||
| /** | ||
| * Support collapsing the sidebar on desktop mode | ||
| * | ||
| * @defaultValue true | ||
| */ | ||
| collapsible?: boolean; | ||
| } | ||
|
|
||
| export function DocsLayout({ | ||
| nav: { transparentMode, ...nav } = {}, | ||
| sidebar: { | ||
| tabs: sidebarTabs, | ||
| enabled: sidebarEnabled = true, | ||
| ...sidebarProps | ||
| } = {}, | ||
| searchToggle = {}, | ||
| disableThemeSwitch = false, | ||
| themeSwitch = { enabled: !disableThemeSwitch }, | ||
| i18n = false, | ||
| children, | ||
| ...props | ||
| }: DocsLayoutProps) { | ||
| const tabs = useMemo(() => { | ||
| if (Array.isArray(sidebarTabs)) { | ||
| return sidebarTabs; | ||
| } | ||
| if (typeof sidebarTabs === "object") { | ||
| return getSidebarTabs(props.tree, sidebarTabs); | ||
| } | ||
| if (sidebarTabs !== false) { | ||
| return getSidebarTabs(props.tree); | ||
| } | ||
| return []; | ||
| }, [sidebarTabs, props.tree]); | ||
| const links = getLinks(props.links ?? [], props.githubUrl); | ||
| const sidebarVariables = cn( | ||
| "md:[--fd-sidebar-width:268px] lg:[--fd-sidebar-width:286px]" | ||
| ); | ||
|
|
||
| function sidebar() { | ||
| const { | ||
| footer, | ||
| banner, | ||
| collapsible = true, | ||
| component, | ||
| components, | ||
| defaultOpenLevel, | ||
| prefetch, | ||
| ...rest | ||
| } = sidebarProps; | ||
| if (component) return component; | ||
|
|
||
| const iconLinks = links.filter( | ||
| (item): item is IconItemType => item.type === "icon" | ||
| ); | ||
|
|
||
| const viewport = ( | ||
| <SidebarViewport> | ||
| {links | ||
| .filter((v) => v.type !== "icon") | ||
| .map((item, i, list) => ( | ||
| <SidebarLinkItem | ||
| key={i} | ||
| item={item} | ||
| className={cn(i === list.length - 1 && "mb-4")} | ||
| /> | ||
| ))} | ||
| <SidebarPageTree components={components} /> | ||
| </SidebarViewport> | ||
| ); | ||
|
|
||
| const mobile = ( | ||
| <SidebarContentMobile {...rest}> | ||
| <SidebarHeader> | ||
| <div className="flex text-fd-muted-foreground items-center gap-1.5"> | ||
| <div className="flex flex-1"> | ||
| {iconLinks.map((item, i) => ( | ||
| <BaseLinkItem | ||
| key={i} | ||
| item={item} | ||
| className={cn( | ||
| buttonVariants({ | ||
| size: "icon-sm", | ||
| color: "ghost", | ||
| className: "p-2", | ||
| }) | ||
| )} | ||
| aria-label={item.label} | ||
| > | ||
| {item.icon} | ||
| </BaseLinkItem> | ||
| ))} | ||
| </div> | ||
| {i18n ? ( | ||
| <LanguageToggle> | ||
| <Languages className="size-4.5" /> | ||
| <LanguageToggleText /> | ||
| </LanguageToggle> | ||
| ) : null} | ||
| {themeSwitch.enabled !== false && | ||
| (themeSwitch.component ?? ( | ||
| <ThemeToggle className="p-0" mode={themeSwitch.mode} /> | ||
| ))} | ||
| <Link | ||
| href={githubLink} | ||
| className="max-w-7 rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100" | ||
| > | ||
| <MdiGithub className="size-full" /> | ||
| </Link> | ||
| <SidebarTrigger | ||
| className={cn( | ||
| buttonVariants({ | ||
| color: "ghost", | ||
| size: "icon-sm", | ||
| className: "p-2", | ||
| }) | ||
| )} | ||
| > | ||
| <SidebarIcon /> | ||
| </SidebarTrigger> | ||
| </div> | ||
| {tabs.length > 0 && <RootToggle options={tabs} />} | ||
| {banner} | ||
| </SidebarHeader> | ||
| {viewport} | ||
| <SidebarFooter className="empty:hidden">{footer}</SidebarFooter> | ||
| </SidebarContentMobile> | ||
| ); | ||
|
|
||
| const content = ( | ||
| <SidebarContent {...rest}> | ||
| <SidebarHeader> | ||
| <div className="flex flex-row gap-2.5 items-center"> | ||
| <img className="size-5" src="/icon/logo.png" alt="Tinyauth" /> | ||
| <Link | ||
| href={nav.url ?? "/"} | ||
| className="inline-flex text-[15px] items-center gap-2.5 font-medium me-auto" | ||
| > | ||
| {nav.title} | ||
| </Link> | ||
| {nav.children} | ||
| {collapsible && ( | ||
| <SidebarCollapseTrigger | ||
| className={cn( | ||
| buttonVariants({ | ||
| color: "ghost", | ||
| size: "icon-sm", | ||
| className: "mb-auto text-fd-muted-foreground", | ||
| }) | ||
| )} | ||
| > | ||
| <SidebarIcon /> | ||
| </SidebarCollapseTrigger> | ||
| )} | ||
| </div> | ||
| {searchToggle.enabled !== false && | ||
| (searchToggle.components?.lg ?? ( | ||
| <LargeSearchToggle hideIfDisabled /> | ||
| ))} | ||
| {tabs.length > 0 && <RootToggle options={tabs} />} | ||
|
|
||
| {banner} | ||
| </SidebarHeader> | ||
| {viewport} | ||
| <HideIfEmpty as={SidebarFooter}> | ||
| <div className="flex text-fd-muted-foreground items-center empty:hidden"> | ||
| <Link | ||
| href={githubLink} | ||
| className="max-w-7 rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100" | ||
| > | ||
| <MdiGithub className="size-full" /> | ||
| </Link> | ||
| {i18n ? ( | ||
| <LanguageToggle> | ||
| <Languages className="size-4.5" /> | ||
| </LanguageToggle> | ||
| ) : null} | ||
| {iconLinks.map((item, i) => ( | ||
| <BaseLinkItem | ||
| key={i} | ||
| item={item} | ||
| className={cn( | ||
| buttonVariants({ size: "icon-sm", color: "ghost" }) | ||
| )} | ||
| aria-label={item.label} | ||
| > | ||
| {item.icon} | ||
| </BaseLinkItem> | ||
| ))} | ||
| {themeSwitch.enabled !== false && | ||
| (themeSwitch.component ?? ( | ||
| <ThemeToggle className="ms-auto p-0" mode={themeSwitch.mode} /> | ||
| ))} |
There was a problem hiding this comment.
Respect the configurable docs githubUrl when rendering buttons
The GitHub buttons are fixed to https://github.com/steveiliop56/tinyauth, so providing githubUrl in DocsLayoutProps has no effect. That breaks the documented API (and duplicate links under iconLinks stay pointed at the wrong repo). Thread the actual value through.
-const githubLink: string = "https://github.com/steveiliop56/tinyauth";
+const DEFAULT_GITHUB_LINK = "https://github.com/steveiliop56/tinyauth";
@@
- const tabs = useMemo(() => {
+ const tabs = useMemo(() => {
if (Array.isArray(sidebarTabs)) {
return sidebarTabs;
}
@@
return [];
}, [sidebarTabs, props.tree]);
- const links = getLinks(props.links ?? [], props.githubUrl);
+ const githubLink = props.githubUrl ?? DEFAULT_GITHUB_LINK;
+ const links = getLinks(props.links ?? [], githubLink);
@@
- <Link
- href={githubLink}
+ <Link
+ href={githubLink}
className="max-w-7 rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100"
>
<MdiGithub className="size-full" />
</Link>
@@
- <Link
- href={githubLink}
+ <Link
+ href={githubLink}
className="max-w-7 rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100"
>
<MdiGithub className="size-full" />
</Link>🤖 Prompt for AI Agents
In app/components/layout/docs/index.tsx lines ~51-271, the GitHub buttons use
the hardcoded githubLink constant instead of the configurable githubUrl from
props; replace uses of githubLink with (props.githubUrl ?? githubLink) so the
provided githubUrl is respected (you can keep the existing githubLink as the
default fallback or remove it and use a string fallback inline), and ensure any
duplicate iconLinks that should point to the repo also use the same
props.githubUrl fallback.
| export function PageTOCTitle(props: ComponentProps<"h2">) { | ||
| return ( | ||
| <h3 | ||
| {...props} | ||
| className={cn( | ||
| "inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground", | ||
| props.className, | ||
| )} | ||
| > | ||
| <Text className="size-4" /> | ||
| <I18nLabel label="toc" /> | ||
| </h3> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Prop type mismatch: typed as h2, rendered as h3
Align the prop type with the rendered element to avoid incorrect attribute typings.
-export function PageTOCTitle(props: ComponentProps<"h2">) {
+export function PageTOCTitle(props: ComponentProps<"h3">) {
return (
- <h3
+ <h3
{...props}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function PageTOCTitle(props: ComponentProps<"h2">) { | |
| return ( | |
| <h3 | |
| {...props} | |
| className={cn( | |
| "inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground", | |
| props.className, | |
| )} | |
| > | |
| <Text className="size-4" /> | |
| <I18nLabel label="toc" /> | |
| </h3> | |
| ); | |
| } | |
| export function PageTOCTitle(props: ComponentProps<"h3">) { | |
| return ( | |
| <h3 | |
| {...props} | |
| className={cn( | |
| "inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground", | |
| props.className, | |
| )} | |
| > | |
| <Text className="size-4" /> | |
| <I18nLabel label="toc" /> | |
| </h3> | |
| ); | |
| } |
🤖 Prompt for AI Agents
In app/components/layout/docs/page.tsx around lines 20 to 33, the component is
typed as ComponentProps<"h2"> but renders an h3, causing prop/attribute type
mismatch; update the component's prop type to ComponentProps<"h3"> (or
alternatively change the rendered element to h2) so the declared props match the
actual DOM element and ensure any imported types are adjusted accordingly.
| const githubLink: string = "https://github.com/steveiliop56/tinyauth"; | ||
|
|
||
| export interface HomeLayoutProps extends BaseLayoutProps { | ||
| nav?: Partial< | ||
| NavOptions & { | ||
| /** | ||
| * Open mobile menu when hovering the trigger | ||
| */ | ||
| enableHoverToOpen?: boolean; | ||
| } | ||
| >; | ||
| } | ||
|
|
||
| export function HomeLayout( | ||
| props: HomeLayoutProps & HTMLAttributes<HTMLElement> | ||
| ) { | ||
| const { | ||
| nav = {}, | ||
| links, | ||
| githubUrl, | ||
| i18n, | ||
| disableThemeSwitch = false, | ||
| themeSwitch = { enabled: !disableThemeSwitch }, | ||
| searchToggle, | ||
| ...rest | ||
| } = props; | ||
|
|
||
| return ( | ||
| <NavProvider transparentMode={nav?.transparentMode}> | ||
| <main | ||
| id="nd-home-layout" | ||
| {...rest} | ||
| className={cn("flex flex-1 flex-col pt-14", rest.className)} | ||
| > | ||
| {nav.enabled !== false && | ||
| (nav.component ?? ( | ||
| <Header | ||
| links={links} | ||
| nav={nav} | ||
| themeSwitch={themeSwitch} | ||
| searchToggle={searchToggle} | ||
| i18n={i18n} | ||
| githubUrl={githubUrl} | ||
| /> | ||
| ))} | ||
| {props.children} | ||
| </main> | ||
| </NavProvider> | ||
| ); | ||
| } | ||
|
|
||
| export function Header({ | ||
| nav = {}, | ||
| i18n = false, | ||
| links, | ||
| githubUrl, | ||
| themeSwitch = {}, | ||
| searchToggle = {}, | ||
| }: HomeLayoutProps) { | ||
| const finalLinks = useMemo( | ||
| () => getLinks(links, githubUrl), | ||
| [links, githubUrl] | ||
| ); | ||
|
|
||
| const navItems = finalLinks.filter((item) => | ||
| ["nav", "all"].includes(item.on ?? "all") | ||
| ); | ||
| const menuItems = finalLinks.filter((item) => | ||
| ["menu", "all"].includes(item.on ?? "all") | ||
| ); | ||
|
|
||
| return ( | ||
| <Navbar> | ||
| <div className="flex flex-row gap-1.5 items-center"> | ||
| <img className="size-5" src="/icon/logo.png" alt="Tinyauth" /> | ||
| <Link | ||
| href={nav.url ?? "/"} | ||
| className="inline-flex items-center gap-2.5 font-semibold" | ||
| > | ||
| {nav.title} | ||
| </Link> | ||
| </div> | ||
| {nav.children} | ||
| <ul className="flex flex-row items-center gap-2 px-6 max-sm:hidden"> | ||
| {navItems | ||
| .filter((item) => !isSecondary(item)) | ||
| .map((item, i) => ( | ||
| <NavbarLinkItem key={i} item={item} className="text-sm" /> | ||
| ))} | ||
| </ul> | ||
| <div className="flex flex-row items-center justify-end gap-1.5 flex-1 max-lg:hidden"> | ||
| {searchToggle.enabled !== false && | ||
| (searchToggle.components?.lg ?? ( | ||
| <LargeSearchToggle | ||
| className="w-full rounded-full ps-2.5 max-w-[240px]" | ||
| hideIfDisabled | ||
| /> | ||
| ))} | ||
| {themeSwitch.enabled !== false && | ||
| (themeSwitch.component ?? <ThemeToggle mode={themeSwitch?.mode} />)} | ||
| <Link | ||
| href={githubLink} | ||
| className="rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100" | ||
| > | ||
| <MdiGithub className="size-full" /> | ||
| </Link> | ||
| {i18n ? ( | ||
| <LanguageToggle> | ||
| <Languages className="size-5" /> | ||
| </LanguageToggle> | ||
| ) : null} | ||
| <div className="flex flex-row items-center empty:hidden"> | ||
| {navItems.filter(isSecondary).map((item, i) => ( | ||
| <NavbarLinkItem key={i} item={item} /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| <ul className="flex flex-row items-center ms-auto -me-1.5 lg:hidden"> | ||
| {searchToggle.enabled !== false && | ||
| (searchToggle.components?.sm ?? ( | ||
| <SearchToggle className="p-2" hideIfDisabled /> | ||
| ))} | ||
| <Menu> | ||
| <MenuTrigger | ||
| aria-label="Toggle Menu" | ||
| className={cn( | ||
| buttonVariants({ | ||
| size: "icon", | ||
| color: "ghost", | ||
| className: "group", | ||
| }) | ||
| )} | ||
| enableHover={nav.enableHoverToOpen} | ||
| > | ||
| <ChevronDown className="!size-5.5 transition-transform duration-300 group-data-[state=open]:rotate-180" /> | ||
| </MenuTrigger> | ||
| <MenuContent className="sm:flex-row sm:items-center sm:justify-end"> | ||
| {menuItems | ||
| .filter((item) => !isSecondary(item)) | ||
| .map((item, i) => ( | ||
| <MenuLinkItem key={i} item={item} className="sm:hidden" /> | ||
| ))} | ||
| <div className="-ms-1.5 flex flex-row items-center gap-1.5 max-sm:mt-2"> | ||
| {menuItems.filter(isSecondary).map((item, i) => ( | ||
| <MenuLinkItem key={i} item={item} className="-me-1.5" /> | ||
| ))} | ||
| <div role="separator" className="flex-1" /> | ||
| {i18n ? ( | ||
| <LanguageToggle> | ||
| <Languages className="size-5" /> | ||
| <LanguageToggleText /> | ||
| <ChevronDown className="size-3 text-fd-muted-foreground" /> | ||
| </LanguageToggle> | ||
| ) : null} | ||
| {themeSwitch.enabled !== false && | ||
| (themeSwitch.component ?? ( | ||
| <ThemeToggle mode={themeSwitch?.mode} /> | ||
| ))} | ||
| <Link | ||
| href={githubLink} | ||
| className="rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100" | ||
| > | ||
| <MdiGithub className="size-full" /> |
There was a problem hiding this comment.
Respect the githubUrl prop when rendering GitHub links
The header always links to https://github.com/steveiliop56/tinyauth, ignoring any githubUrl passed through HomeLayoutProps. This breaks configurability (and can render duplicate/mismatched icons when consumers supply their own link via links or githubUrl). Please derive the GitHub href from the prop (with a sensible default) and keep the rendered link in sync with what you pass into getLinks.
-const githubLink: string = "https://github.com/steveiliop56/tinyauth";
+const DEFAULT_GITHUB_URL = "https://github.com/steveiliop56/tinyauth";
@@
- githubUrl,
+ githubUrl = DEFAULT_GITHUB_URL,
@@
- const finalLinks = useMemo(
- () => getLinks(links, githubUrl),
- [links, githubUrl]
- );
+ const finalLinks = useMemo(
+ () => getLinks(links, githubUrl),
+ [links, githubUrl]
+ );
@@
- <Link
- href={githubLink}
- className="rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100"
- >
- <MdiGithub className="size-full" />
- </Link>
+ {githubUrl ? (
+ <Link
+ href={githubUrl}
+ className="rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100"
+ >
+ <MdiGithub className="size-full" />
+ </Link>
+ ) : null}
@@
- <Link
- href={githubLink}
- className="rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100"
- >
- <MdiGithub className="size-full" />
- </Link>
+ {githubUrl ? (
+ <Link
+ href={githubUrl}
+ className="rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100"
+ >
+ <MdiGithub className="size-full" />
+ </Link>
+ ) : null}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/components/layout/home/index.tsx around lines 27 to 189, the Header
hardcodes githubLink to "https://github.com/steveiliop56/tinyauth" instead of
using the githubUrl prop passed into HomeLayoutProps; update the component to
derive a single finalGithubUrl (use githubUrl prop with the existing constant as
fallback) and replace all uses of the hardcoded githubLink with that
finalGithubUrl so both getLinks and rendered <Link> use the same resolved URL;
ensure finalGithubUrl is computed in HomeLayout and passed down to Header (or
computed in Header from props) and remove the top-level constant to avoid
divergence.
| <EditOnGitHub | ||
| href={`https://github.com/${editOnGithub.owner}/${editOnGithub.repo}/blob/${editOnGithub.sha}/${editOnGithub.path.startsWith("/") ? editOnGithub.path.slice(1) : editOnGithub.path}`} | ||
| /> |
There was a problem hiding this comment.
Fix missing default for the edit link SHA.
When editOnGithub.sha isn’t provided we end up linking to .../blob/undefined/..., breaking the “Edit on GitHub” shortcut. Default the SHA/ref to "main" (or the project’s default branch) before constructing the URL.
Apply this diff:
- {editOnGithub && (
- <EditOnGitHub
- href={`https://github.com/${editOnGithub.owner}/${editOnGithub.repo}/blob/${editOnGithub.sha}/${editOnGithub.path.startsWith("/") ? editOnGithub.path.slice(1) : editOnGithub.path}`}
- />
- )}
+ {editOnGithub && (
+ <EditOnGitHub
+ href={`https://github.com/${editOnGithub.owner}/${editOnGithub.repo}/blob/${editOnGithub.sha ?? "main"}/${editOnGithub.path.startsWith("/") ? editOnGithub.path.slice(1) : editOnGithub.path}`}
+ />
+ )}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <EditOnGitHub | |
| href={`https://github.com/${editOnGithub.owner}/${editOnGithub.repo}/blob/${editOnGithub.sha}/${editOnGithub.path.startsWith("/") ? editOnGithub.path.slice(1) : editOnGithub.path}`} | |
| /> | |
| {editOnGithub && ( | |
| <EditOnGitHub | |
| href={`https://github.com/${editOnGithub.owner}/${editOnGithub.repo}/blob/${editOnGithub.sha ?? "main"}/${editOnGithub.path.startsWith("/") ? editOnGithub.path.slice(1) : editOnGithub.path}`} | |
| /> | |
| )} |
🤖 Prompt for AI Agents
In app/components/layout/page.tsx around lines 183 to 185, the URL template uses
editOnGithub.sha directly which can be undefined and produces a broken link;
before constructing the href, default the SHA/ref to "main" (e.g. const sha =
editOnGithub.sha ?? "main") and use that variable in the URL template, keeping
the existing path normalization (slice(1) if startsWith("/")) and avoid mutating
the original editOnGithub object.
| import type { BaseLinkType } from './index'; | ||
|
|
||
| export function BaseLinkItem({ | ||
| ref, |
There was a problem hiding this comment.
Unused ref parameter.
The ref parameter is destructured but never used in the component. If forwarding refs is not required, remove it from the destructuring. If refs should be forwarded to the Link component, pass it via the ref prop.
Apply this diff if ref forwarding is not needed:
export function BaseLinkItem({
- ref,
item,
...props
}: Omit<ComponentProps<'a'>, 'href'> & { item: BaseLinkType }) {Or if ref forwarding is intended, apply this diff:
return (
<Link
+ ref={ref}
href={item.url}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ref, | |
| export function BaseLinkItem({ | |
| item, | |
| ...props | |
| }: Omit<ComponentProps<'a'>, 'href'> & { item: BaseLinkType }) { | |
| return ( | |
| <Link | |
| href={item.url} | |
| {...props} | |
| > | |
| {item.label} | |
| </Link> | |
| ) | |
| } |
| ref, | |
| export function BaseLinkItem({ | |
| ref, | |
| item, | |
| ...props | |
| }: Omit<ComponentProps<'a'>, 'href'> & { item: BaseLinkType }) { | |
| return ( | |
| <Link | |
| ref={ref} | |
| href={item.url} | |
| {...props} | |
| > | |
| {item.label} | |
| </Link> | |
| ) | |
| } |
🤖 Prompt for AI Agents
In app/components/layout/shared/client.tsx around line 9, the ref parameter is
destructured but never used; either remove ref from the component
props/destructuring if ref forwarding is not required, or implement ref
forwarding by accepting a forwarded ref and passing it to the underlying Link
(and wrap the component with React.forwardRef or the equivalent), ensuring prop
types are updated to include the ref type.
| | `USERS` | `--users` | Comma-separated list of users in the format `username:hash`. | `` | yes | | ||
| | `USERS_FILE` | `--users-file` | Path to a file containing users in the format `username:hash`. | `` | no | | ||
| | `SECURE_COOKIE` | `--secure-cookie` | Send cookie over secure connection only. | `false` | no | | ||
| | `OAUTH_WHITELIST` | `--oauth-whitelist` | Comma-separated list of email addresses to whitelist when using OAuth. | `` | no | | ||
| | `OAUTH_AUTO_REDIRECT` | `--oauth-auto-redirect` | Auto redirect to the specified OAuth provider. | `` | no | | ||
| | `SESSION_EXPIRY` | `--session-expiry` | Session (cookie) expiration time in seconds. | `86400` | no | | ||
| | `LOGIN_TIMEOUT` | `--login-timeout` | Login timeout in seconds after max retries reached (0 to disable). | `300` | no | | ||
| | `LOGIN_MAX_RETRIES` | `--login-max-retries` | Maximum login attempts before timeout (0 to disable). | `5` | no | | ||
| | `LOG_LEVEL` | `--log-level` | Log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`). | `info` | no | | ||
| | `APP_TITLE` | `--app-title` | Title of the app. | `Tinyauth` | no | | ||
| | `FORGOT_PASSWORD_MESSAGE` | `--forgot-password-message` | Message to show on the forgot password page. | `` | no | | ||
| | `BACKGROUND_IMAGE` | `--background-image` | Background image URL for the login page. | `/background.jpg` | no | | ||
| | `RESOURCES_DIR` | `--resources-dir` | Path to a directory containing custom resources (e.g., background image). | `/data/resources` | no | | ||
| | `DATABASE_PATH` | `--database-path` | Path to the SQLite database file. | `/data/tinyauth.db` | no | | ||
| | `TRUSTED_PROXIES` | `--trusted-proxies` | Comma-separated list of trusted proxies (IP addresses or CIDRs) for correct client IP detection. | `` | no | | ||
| | `DISABLE_ANALYTICS` | `--disable-analytics` | Disable anonymous version collection. | `false` | no | | ||
| | `DISABLE_RESOURCES` | `--disable-resources` | Disable the resources server. | `false` | no | | ||
|
|
||
| ## OAuth Config | ||
|
|
||
| OAuth configuration includes a provider ID in the environment variable or flag name. For example, `PROVIDERS_GOOGLE_CLIENT_ID`. | ||
|
|
||
| | Environment | Flag | Description | Default | Required | | ||
| | ------------------------------------- | --------------------------------------- | ---------------------------------------- | ------- | -------- | | ||
| | `PROVIDERS_[ID]_CLIENT_ID` | `--providers-[id]-client-id` | OAuth client ID. | `` | yes | | ||
| | `PROVIDERS_[ID]_CLIENT_SECRET` | `--providers-[id]-client-secret` | OAuth client secret. | `` | yes | | ||
| | `PROVIDERS_[ID]_CLIENT_SECRET_FILE` | `--providers-[id]-client-secret-file` | Path to a file containing client secret. | `` | no | |
There was a problem hiding this comment.
Clarify “Required” when a FILE_ alternative exists.
Rows like USERS (Lines 21-22) and PROVIDERS_[ID]_CLIENT_SECRET (Lines 45-47) are marked “Required: yes”, yet the adjacent FILE_ variants allow the same data to be provided via file. With the current wording, readers may think both values must be supplied simultaneously. Please adjust the Required column (e.g., “Yes — unless using USERS_FILE”) or add a footnote so the requirement is unambiguous.
🤖 Prompt for AI Agents
In content/docs/reference/configuration.mdx around lines 21 to 47, the
"Required" column incorrectly implies both primary and FILE_ variants must be
provided; update the table entries for rows like USERS/USERS_FILE and
PROVIDERS_[ID]_CLIENT_SECRET/PROVIDERS_[ID]_CLIENT_SECRET_FILE to indicate the
primary key is required unless the corresponding _FILE variant is used (e.g.,
change "yes" to "Yes — or provide USERS_FILE" or add a footnote explaining "Yes
unless using the corresponding _FILE variable"), and apply the same phrasing for
PROVIDERS_[ID]_CLIENT_SECRET so readers clearly understand the file alternative
satisfies the requirement.
| Custom headers can be set using the `tinyauth.headers` label on any container that uses the Tinyauth middleware. For example: | ||
|
|
||
| ```yaml | ||
| tinyauth.apps.[app].response.headers: my-header=cool | ||
| ``` |
There was a problem hiding this comment.
Align label name with actual configuration key.
Line 33 calls out the tinyauth.headers label, but the example immediately below uses tinyauth.apps.[app].response.headers. One of these names is wrong; as written it sends readers hunting for a label that doesn’t exist. Please update the text and example so they reference the same (correct) label.
🤖 Prompt for AI Agents
In content/docs/reference/headers.mdx around lines 33 to 37, the text calls out
the label `tinyauth.headers` while the example uses
`tinyauth.apps.[app].response.headers`; update both the descriptive sentence and
the YAML example to reference the same, correct configuration key (choose the
actual canonical key used by the codebase—likely
`tinyauth.apps.[app].response.headers`), replace `tinyauth.headers` with that
canonical key in the prose, keep the example using the same key format, and
ensure the placeholder `[app]` is left or explained consistently.
| { | ||
| "name": "tinyauth-docs", | ||
| "version": "0.1.0", | ||
| "name": "example-react-router", |
There was a problem hiding this comment.
Rename package to match the project
name is example-react-router. Consider tinyauth-docs for clarity.
- "name": "example-react-router",
+ "name": "tinyauth-docs",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "name": "example-react-router", | |
| "name": "tinyauth-docs", |
🤖 Prompt for AI Agents
In package.json at line 2, the project's "name" field is currently
"example-react-router" but should reflect the actual project; change it to
"tinyauth-docs". Update the name value to "tinyauth-docs" ensuring it follows
npm package name rules (lowercase, no spaces) and save the file; if this package
is published or used as a dependency, also update any related references or
documentation to match the new name.
| /// <reference types="vite/client" /> | ||
| import { fromConfig } from 'fumadocs-mdx/runtime/vite'; | ||
| import type * as Config from './source.config'; | ||
|
|
||
| export const create = fromConfig<typeof Config>(); | ||
|
|
||
| export const docs = { | ||
| doc: create.doc("docs", "./content/docs", import.meta.glob(["./**/*.{mdx,md}"], { | ||
| "base": "./content/docs", | ||
| "query": { | ||
| "collection": "docs" | ||
| } | ||
| })), | ||
| meta: create.meta("docs", "./content/docs", import.meta.glob(["./**/*.{json,yaml}"], { | ||
| "import": "default", | ||
| "base": "./content/docs", | ||
| "query": { | ||
| "collection": "docs" | ||
| } | ||
| })) | ||
| }; No newline at end of file |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Verify generated file is in .gitignore.
This appears to be a generated file based on the naming convention and structure. Ensure source.generated.ts is excluded from version control if it's auto-generated during build.
Run the following script to check:
🏁 Script executed:
#!/bin/bash
# Check if source.generated.ts is in .gitignore
if grep -q "source.generated.ts" .gitignore; then
echo "✓ File is in .gitignore"
else
echo "⚠ File is NOT in .gitignore - consider adding it if it's generated at build time"
echo ""
echo "Checking if fumadocs has a generation command:"
cat package.json | jq -r '.scripts | to_entries[] | select(.value | contains("fumadocs")) | "\(.key): \(.value)"'
fiLength of output: 128
Add source.generated.ts to .gitignore
Prevent committing this auto-generated file from fumadocs-mdx.
🤖 Prompt for AI Agents
In source.generated.ts around lines 1 to 21, this file is auto-generated by
fumadocs-mdx and should not be committed; add the path "source.generated.ts" (or
the exact relative path to this file) to the project's .gitignore (or update an
existing rule to match generated files, e.g., add a pattern for generated MDX
runtime outputs) so that Git ignores it going forward, then commit the updated
.gitignore.
| server: { | ||
| allowedHosts: true, | ||
| }, |
There was a problem hiding this comment.
Review allowedHosts: true for security implications.
Setting allowedHosts: true disables host header validation, which can expose the dev server to DNS rebinding attacks. This setting should only be used in trusted development environments.
Consider restricting to specific hosts in production or shared environments:
server: {
- allowedHosts: true,
+ allowedHosts: ['.example.com', 'localhost'],
},🤖 Prompt for AI Agents
In vite.config.ts around lines 10 to 12, allowedHosts is set to true which
disables host header validation and can enable DNS rebinding attacks; change
this to a safer configuration by restricting allowedHosts to an explicit list of
trusted hostnames (e.g., ['localhost', '127.0.0.1', '.your-dev-domain.com']) and
conditionally enable the permissive setting only for a local/trusted environment
using an environment flag (NODE_ENV or a custom VITE_DEV_TRUSTED flag), ensuring
production or shared environments never use allowedHosts: true.
|
@Preview deploy |
1 similar comment
|
@Preview deploy |
There was a problem hiding this comment.
Actionable comments posted: 10
♻️ Duplicate comments (27)
content/docs/community/kubernetes.mdx (1)
59-63: Fix the bcrypt example hashDoubling the
$characters in the bcrypt string produces a completely different hash, so anyone copying this manifest won’t be able to log in. Restore the single-dollar bcrypt value ($2a$10$…) exactly as generated.app/components/ui/scroll-area.tsx (1)
53-53: Critical: Fix the incorrect Radix Thumb API usage.The component uses
ScrollAreaPrimitive.ScrollAreaThumbwhich does not exist in the Radix UI API. This will cause a runtime crash. The correct primitive isScrollAreaPrimitive.Thumb.Apply this diff to fix the issue:
- <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-fd-border" /> + <ScrollAreaPrimitive.Thumb className="relative flex-1 rounded-full bg-fd-border" />Based on learnings.
app/components/ui/toc.tsx (1)
22-26: Critical: TOCContext rendering issue remains unfixedThe previously reported critical issue is still present.
TOCContextis a context object created bycreateContext, not a React component. Attempting to render<TOCContext value={toc}>will throw a runtime error. You must useTOCContext.Providerinstead.Apply this diff to fix:
- <TOCContext value={toc}> + <TOCContext.Provider value={toc}> <Primitive.AnchorProvider toc={toc} {...props}> {children} </Primitive.AnchorProvider> - </TOCContext> + </TOCContext.Provider>vite.config.ts (1)
10-12: Security concern withallowedHosts: truealready flagged.A previous review comment correctly identified that
allowedHosts: truedisables host header validation and can expose the dev server to DNS rebinding attacks.package.json (1)
2-2: Package name issue already flagged.A previous review comment correctly identified that the package name should be
tinyauth-docsinstead ofexample-react-routerto match the project.app/components/search-toggle.tsx (1)
15-79: Address prop forwarding and accessibility issues.As noted in the previous review, both
SearchToggleandLargeSearchToggleshould:
- Forward all remaining button props to the rendered element
- Merge consumer
onClickwith internal handler (call consumer first)- Add
disabled={!enabled}to prevent interaction when search is unavailableThese changes improve component composability and accessibility semantics.
app/components/ui/toc-thumb.tsx (1)
13-31: Guard against unresolved anchors in calc function.As identified in the previous review, when no active anchors are found in the container,
upperremainsNumber.MAX_VALUE, resulting in invalid CSS variable values that cause visual glitches.The fix is to check if anchors were resolved and return
[0, 0]early, plus clamp the height to non-negative values.app/components/ui/toc-clerk.tsx (1)
36-38: Escape CSS special characters in querySelector.As noted in the previous review, the querySelector construction at line 37 can fail when TOC item URLs contain CSS special characters (
:,.,[, etc.). The fragment portion must be escaped usingCSS.escape()before interpolating into the selector string.app/components/layout/page.tsx (1)
183-185: Default the GitHub SHA to prevent broken links.As identified in the previous review, when
editOnGithub.shais undefined, the constructed URL contains/blob/undefined/..., creating a broken GitHub link. The SHA should default to"main"(or the project's default branch) before URL construction, as documented in the JSDoc comment.app/components/layout/home/index.tsx (1)
27-27: Hardcoded GitHub link ignoresgithubUrlprop.The
githubLinkconstant is hardcoded and used in the rendered links (lines 128, 186), but the component receivesgithubUrlas a prop and passes it togetLinks(lines 86-89). This inconsistency means the rendered GitHub icon links will always point to the hardcoded URL, ignoring any customgithubUrlprovided by consumers.Also applies to: 128-128, 186-186
content/docs/guides/using-the-binary.mdx (1)
32-39: Replace the export/unset snippets with a robust.envloader.The current
export $(grep -v '^#' .env | xargs -d '\n')pattern and the correspondingunsetsnippet are brittle: they break on values containing spaces or quotes, and-d '\n'is GNU-specific. This issue was previously flagged.Apply this diff to use a POSIX-safe pattern:
-```sh -export $(grep -v '^#' .env | xargs -d '\n') -``` - -<Callout type="tip"> - To unset the environment variables for security purposes, use: `unset $(grep - -v '^#' .env | sed -E 's/(.*)=.*/\1/' | xargs)`. -</Callout> +```sh +set -a +. ./.env +set +a +``` + +<Callout type="tip"> + To unset specific environment variables, use explicit `unset KEY` commands for each variable defined in `.env`. +</Callout>content/docs/reference/headers.mdx (1)
32-39: Align label name with actual configuration key.Line 33 refers to the
tinyauth.headerslabel, but Line 36 showstinyauth.apps.[app].response.headers. This inconsistency was previously flagged and remains unresolved. One of these names is incorrect, leading to confusion.Apply this diff to correct the reference:
-Custom headers can be set using the `tinyauth.headers` label on any container that uses the Tinyauth middleware. For example: +Custom headers can be set using the `tinyauth.apps.[app].response.headers` label on any container that uses the Tinyauth middleware. For example:app/routes/home.tsx (1)
9-17: Remove the empty object destructure inmeta.Line 9 still triggers Biome’s
noEmptyPatternerror. Drop the empty pattern or rename the unused arg to_argsto unblock the linter. Based on static analysis hints.-export function meta({}: Route.MetaArgs) { +export function meta(_: Route.MetaArgs) {content/docs/guides/google-oauth.mdx (1)
70-75: Update the warning to reference Google accounts.This guide is about Google OAuth, but Line 71 still says “GitHub account,” which is misleading.
- OAuth alone does not guarantee security. By default, any GitHub account can + OAuth alone does not guarantee security. By default, any Google account cancontent/docs/getting-started.mdx (1)
102-105: Remove--api.insecure=trueor add a clear non-production warning.The
--api.insecure=trueflag exposes the Traefik dashboard/API without authentication. While port 8080 isn't published in this example, users often copy these configurations to production environments where it creates a security risk.Consider one of these approaches:
Option 1 (preferred): Remove the insecure flag entirely:
- command: --api.insecure=true --providers.docker + command: --providers.dockerOption 2: Add a prominent warning and secure alternative:
command: --api.insecure=true --providers.docker + # WARNING: --api.insecure=true is for local testing only! + # For production, secure the dashboard with authentication or bind to 127.0.0.1content/docs/guides/pocket-id.mdx (1)
84-89: Fix the relative link extension for MDX navigation.The link
./access-controls.md#label-discoverywill 404 because the target is now an.mdxfile in the new docs system. Remove the.mdextension so the link resolver can find the page correctly.Apply this fix:
- controls](./access-controls.md#label-discovery) guide for more information. + controls](./access-controls#label-discovery) guide for more information.content/docs/guides/nginx-proxy-manager.mdx (1)
102-111: Add request body guards to prevent performance issues.The
/tinyauthauth_request location will stream full client request bodies (including large file uploads and POST data) to Tinyauth, causing significant latency and potential timeouts. Auth subrequests should not forward request bodies.Apply these changes to optimize the auth subrequest:
# Tinyauth auth request location /tinyauth { + internal; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; # Pass request to Tinyauth proxy_pass http://tinyauth:3000/api/auth/nginx; # Pass the request headers proxy_set_header x-forwarded-proto $scheme; proxy_set_header x-forwarded-host $http_host; proxy_set_header x-forwarded-uri $request_uri; }This keeps auth checks fast and prevents timeouts on large uploads.
content/docs/reference/cli.mdx (1)
96-99: Fix copy-paste error in TOTP flag description.The
--userflag description in the Generate TOTP Command table is incorrect. It says "combination to verify" but this section generates TOTP secrets, not verifies credentials.Apply this correction:
| Flag | Description | Default | Required | | --------------- | -------------------------------------------------- | ------- | -------- | -| `--user` | The `username:hash` combination to verify. | `` | yes | +| `--user` | The `username:hash` to generate the TOTP secret from. | `` | yes | | `--interactive` | Use an interactive TUI for creating the TOTP user. | `false` | no |app/lib/is-active.ts (1)
1-23: Stop importing private SidebarTab typesWe’re still pulling
SidebarTabfrom the internalfumadocs-ui/utils/get-sidebar-tabspath that can disappear in a patch release. Please swap to a public export or declare the minimal shape locally—this mirrors the earlier feedback.-import type { SidebarTab } from 'fumadocs-ui/utils/get-sidebar-tabs'; +type SidebarTabLike = { + url: string; + urls?: Set<string>; +}; ... -export function isTabActive(tab: SidebarTab, pathname: string) { +export function isTabActive(tab: SidebarTabLike, pathname: string) {app/docs/page.tsx (1)
27-52: Move metadata into the route’smetaexportHead tags are still rendered inside the component body, ignoring React Router’s
metaAPI and the previous review. Please expose the frontmatter through the loader and return the descriptors frommeta, then drop the inline<title>/<meta>elements.export async function loader({ params }: Route.LoaderArgs) { const slugs = params["*"].split("/").filter((v) => v.length > 0); const page = source.getPage(slugs); if (!page) throw new Response("Not found", { status: 404 }); return { path: page.path, tree: source.pageTree, + frontmatter: page.data?.frontmatter ?? page.data, }; } +export function meta({ data }: Route.MetaArgs) { + const frontmatter = data?.frontmatter; + return [ + { title: frontmatter?.title ?? "Docs" }, + frontmatter?.description + ? { name: "description", content: frontmatter.description } + : null, + ].filter(Boolean); +} + const renderer = toClientRenderer( docs.doc, ({ toc, default: Mdx, frontmatter }) => { return ( <DocsPage toc={toc}> - <title>{frontmatter.title}</title> - <meta name="description" content={frontmatter.description} /> <DocsTitle>{frontmatter.title}</DocsTitle> <DocsDescription>{frontmatter.description}</DocsDescription>app/components/mdx/mermaid.tsx (2)
3-15: Add a Suspense boundary and avoid returningundefined
use()throws a promise the first time the module renders, so without aSuspenseboundary the component will crash. It also still returnsundefinedbefore mount. Please wrapMermaidContentinSuspenseand returnnull, as already requested.-import { use, useEffect, useId, useState } from "react"; +import { Suspense, use, useEffect, useId, useState } from "react"; ... - if (!mounted) return; - return <MermaidContent chart={chart} />; + if (!mounted) return null; + + return ( + <Suspense fallback={<div aria-busy="true" className="h-8" />}> + <MermaidContent chart={chart} /> + </Suspense> + );
38-59: Harden the Mermaid renderer against XSS
securityLevel: "loose"plus unsanitizeddangerouslySetInnerHTMLleaves the docs vulnerable to injected scripts—exactly the issue highlighted before. Switch Mermaid tosecurityLevel: "strict"and sanitize the SVG (e.g. with DOMPurify) before injecting it.- mermaid.initialize({ + mermaid.initialize({ startOnLoad: false, - securityLevel: "loose", + securityLevel: "strict", fontFamily: "inherit", themeCSS: "margin: 1.5rem auto 0;", theme: resolvedTheme === "dark" ? "dark" : "default", }); @@ - const { svg, bindFunctions } = use( + const { svg, bindFunctions } = use( cachePromise(`${chart}-${resolvedTheme}`, () => { return mermaid.render(id, chart.replaceAll("\\n", "\n")); }) ); - return ( + const sanitizedSvg = DOMPurify.sanitize(svg, { USE_PROFILES: { svg: true } }); + + return ( <div ref={(container) => { if (container) bindFunctions?.(container); }} - dangerouslySetInnerHTML={{ __html: svg }} + dangerouslySetInnerHTML={{ __html: sanitizedSvg }} /> );Add
import DOMPurify from "dompurify";near the top.app/components/root-toggle.tsx (1)
71-97: Compose consumer and internal click handlersSpreading
item.propsafteronClick={onClick}still allows a consumer-suppliedonClickto overwrite the popover-closing logic—exactly what the previous review flagged. Extract the consumer handler, spread the rest, and invoke ours unless the event was prevented.- return ( + const { onClick: itemOnClick, ...itemProps } = item.props ?? {}; + + return ( <Link key={item.url} href={item.url} - onClick={onClick} - {...item.props} + {...itemProps} + onClick={(event) => { + itemOnClick?.(event); + if (!event.defaultPrevented) { + onClick(); + } + }}app/components/navigation-menu.tsx (1)
10-23: Fix broken NavigationMenuItem primitive usage
Primitive.NavigationMenuItemisn’t exported by@radix-ui/react-navigation-menu; the correct primitive isPrimitive.Item. As written, this component isundefined, causing type errors and runtime crashes. Swap every reference (types, JSX, displayName) over toPrimitive.Item.-const NavigationMenuItem = React.forwardRef< - React.ComponentRef<typeof Primitive.NavigationMenuItem>, - React.ComponentPropsWithoutRef<typeof Primitive.NavigationMenuItem> -> +const NavigationMenuItem = React.forwardRef< + React.ComponentRef<typeof Primitive.Item>, + React.ComponentPropsWithoutRef<typeof Primitive.Item> +> (({ className, children, ...props }, ref) => ( - <Primitive.NavigationMenuItem + <Primitive.Item ref={ref} className={cn('list-none', className)} {...props} > {children} - </Primitive.NavigationMenuItem> + </Primitive.Item> )); -NavigationMenuItem.displayName = Primitive.NavigationMenuItem.displayName; +NavigationMenuItem.displayName = Primitive.Item.displayName;app/components/layout/docs/page.tsx (1)
20-32: Align PageTOCTitle prop typing with the rendered element
PageTOCTitlerenders an<h3>but its props are typed asComponentProps<'h2'>. This leaks the wrong attributes to consumers and fights JSX inference. Update the prop typing (or render<h2>). The previous review already called this out.-export function PageTOCTitle(props: ComponentProps<'h2'>) { +export function PageTOCTitle(props: ComponentProps<'h3'>) {app/components/layout/docs/index.tsx (1)
51-251: HonorgithubUrlinstead of hardcoding the repoThe layout lets callers supply
githubUrl, but the rendered GitHub buttons still point tohttps://github.com/steveiliop56/tinyauth. That breaks the public API and duplicates icons when a custom repo is provided. Thread the computed URL through everything (including the sidebar buttons) instead of the constant.-const githubLink: string = "https://github.com/steveiliop56/tinyauth"; +const DEFAULT_GITHUB_LINK = "https://github.com/steveiliop56/tinyauth"; @@ - const links = getLinks(props.links ?? [], props.githubUrl); + const githubUrl = props.githubUrl ?? DEFAULT_GITHUB_LINK; + const links = getLinks(props.links ?? [], props.githubUrl); @@ - <Link - href={githubLink} + <Link + href={githubUrl} className="max-w-7 rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100" > <MdiGithub className="size-full" /> </Link> @@ - <Link - href={githubLink} + <Link + href={githubUrl} className="max-w-7 rounded-full text-fd-muted-foreground border p-1 hover:opacity-80 transform-colors delay-100" > <MdiGithub className="size-full" /> </Link>Also consider reusing
githubUrlwhen derivinglinksto avoid mismatched icons. Based on learnings.app/components/layout/shared/index.tsx (1)
166-170: Fix GitHub capitalizationChange “Github” to “GitHub” for consistency.
{ type: 'icon', url: githubUrl, - text: 'Github', + text: 'GitHub', label: 'GitHub',
🧹 Nitpick comments (5)
package.json (1)
21-21: Consider using caret range for fumadocs-mdx.Line 21 uses an exact version
12.0.1forfumadocs-mdxwhile other fumadocs packages use caret ranges (^15.8.1). Unless this exact pin is intentional for compatibility reasons, consider using^12.0.1for consistency.- "fumadocs-mdx": "12.0.1", + "fumadocs-mdx": "^12.0.1",app/components/language-toggle.tsx (1)
59-68: Consider early return for missing locale text.LanguageToggleText renders an empty
<span>when no matching locale is found. While not harmful, returningnullwould be more semantically correct when there's nothing to display.Apply this diff:
export function LanguageToggleText( props: HTMLAttributes<HTMLSpanElement>, ): React.ReactElement { const context = useI18n(); const text = context.locales?.find( (item) => item.locale === context.locale, )?.name; + if (!text) return null; + return <span {...props}>{text}</span>; }app/components/theme-toggle.tsx (1)
57-67: Clarify the early return in map.Line 58 uses
if (key === "system") return;which implicitly returnsundefined. While functional, explicitly returningnullis clearer and more idiomatic for React rendering.Apply this diff:
{full.map(([key, Icon]) => { - if (key === "system") return; + if (key === "system") return null; return (content/docs/guides/totp.mdx (1)
10-16: Clarify the user creation flow.Line 10 states "This requires the current
username:hash" but Line 13 instructs runningtotp generate --interactive, which prompts for a user. It's unclear whether:
- The user must already exist in the Tinyauth user list before running
totp generate, or- The command creates a new user entry (as suggested by "Use the Tinyauth CLI to create the new user").
If the user must already exist, state "Ensure the user is already configured in Tinyauth" before the command. If the command creates a new user, clarify that this is a user creation step, not an update. Additionally, Line 16 mentions copying the user "displayed after the
user=log message" and adding it to the user list—this implies the command outputs a complete user string, but the phrasing "newly generated user" is ambiguous.Consider revising to:
-A TOTP secret must first be generated. This requires the current `username:hash`. Use the Tinyauth CLI to create the new user: +A TOTP secret must first be generated. Use the Tinyauth CLI to generate a TOTP-enabled user entry from an existing `username:hash`:or, if this is a user creation step:
-A TOTP secret must first be generated. This requires the current `username:hash`. Use the Tinyauth CLI to create the new user: +A TOTP secret must first be generated. Use the Tinyauth CLI to create a new TOTP-enabled user:and clarify Line 16:
-The command prompts for the user and generates a QR code to scan with an authenticator app. Once added, copy the newly generated user (displayed after the `user=` log message) and include it in the Tinyauth user list. +The command prompts for the username and password, generates a QR code to scan with an authenticator app, and outputs a complete user string (after the `user=` log message). Copy this string and add it to the Tinyauth user list (via `USERS` or `USERS_FILE`).app/components/sidebar.tsx (1)
474-516: Use stable keys instead of array index in SidebarPageTreeIndex keys (
key={i}) can cause incorrect reconciliation on reorders. Prefer a stable identifier (e.g.,item.urlor${level}:${item.name}) for separators/folders.- if (Separator) return <Separator key={i} item={item} />; + if (Separator) return <Separator key={`sep-${level}-${i}`} item={item} />; @@ - <PageTreeFolder key={i} item={item}> + <PageTreeFolder key={`folder-${level}-${item.name}-${i}`} item={item}>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (29)
bun.lockis excluded by!**/*.lockpublic/favicon.icois excluded by!**/*.icopublic/icon/logo.pngis excluded by!**/*.pngpublic/screenshots/github-app-client-secret.pngis excluded by!**/*.pngpublic/screenshots/github-app-email.pngis excluded by!**/*.pngpublic/screenshots/github-app-home.pngis excluded by!**/*.pngpublic/screenshots/github-app-new.pngis excluded by!**/*.pngpublic/screenshots/github-new-oauth-app.pngis excluded by!**/*.pngpublic/screenshots/github-oauth-app-homepage.pngis excluded by!**/*.pngpublic/screenshots/github-oauth-client-secret.pngis excluded by!**/*.pngpublic/screenshots/google-cloud-home.pngis excluded by!**/*.pngpublic/screenshots/google-cloud-oauth-configure.pngis excluded by!**/*.pngpublic/screenshots/google-cloud-oauth-created.pngis excluded by!**/*.pngpublic/screenshots/google-cloud-oauth-home.pngis excluded by!**/*.pngpublic/screenshots/lldap-create-user.pngis excluded by!**/*.pngpublic/screenshots/lldap-groups.pngis excluded by!**/*.pngpublic/screenshots/npm-nginx.pngis excluded by!**/*.pngpublic/screenshots/npm-tinyauth.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-client-page.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-group-home.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-home.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-new-client.pngis excluded by!**/*.pngpublic/screenshots/pocket-id-new-group.pngis excluded by!**/*.pngpublic/screenshots/runtipi-enable-advanced-settings.pngis excluded by!**/*.pngpublic/screenshots/runtipi-forward-auth-url.pngis excluded by!**/*.pngpublic/screenshots/runtipi-install-app-example.pngis excluded by!**/*.pngpublic/screenshots/tinyauth-dark.pngis excluded by!**/*.pngpublic/screenshots/zitadel-configuration-view.pngis excluded by!**/*.pngpublic/screenshots/zitadel-redirect-view.pngis excluded by!**/*.png
📒 Files selected for processing (101)
.gitignore(1 hunks).prettierignore(1 hunks).vscode/settings.json(1 hunks)app/app.css(1 hunks)app/cli.json(1 hunks)app/components/card.tsx(1 hunks)app/components/discord.tsx(1 hunks)app/components/github.tsx(1 hunks)app/components/language-toggle.tsx(1 hunks)app/components/layout/docs/client.tsx(1 hunks)app/components/layout/docs/index.tsx(1 hunks)app/components/layout/docs/page-client.tsx(1 hunks)app/components/layout/docs/page.tsx(1 hunks)app/components/layout/home/index.tsx(1 hunks)app/components/layout/home/menu.tsx(1 hunks)app/components/layout/home/navbar.tsx(1 hunks)app/components/layout/page.tsx(1 hunks)app/components/layout/shared/client.tsx(1 hunks)app/components/layout/shared/index.tsx(1 hunks)app/components/mdx-components.tsx(1 hunks)app/components/mdx/mermaid.tsx(1 hunks)app/components/navigation-menu.tsx(1 hunks)app/components/root-toggle.tsx(1 hunks)app/components/search-toggle.tsx(1 hunks)app/components/sidebar.tsx(1 hunks)app/components/theme-toggle.tsx(1 hunks)app/components/ui/button.tsx(1 hunks)app/components/ui/collapsible.tsx(1 hunks)app/components/ui/popover.tsx(1 hunks)app/components/ui/scroll-area.tsx(1 hunks)app/components/ui/toc-clerk.tsx(1 hunks)app/components/ui/toc-thumb.tsx(1 hunks)app/components/ui/toc.tsx(1 hunks)app/docs/page.tsx(1 hunks)app/docs/search.ts(1 hunks)app/lib/cn.ts(1 hunks)app/lib/is-active.ts(1 hunks)app/lib/layout.shared.tsx(1 hunks)app/lib/merge-refs.ts(1 hunks)app/lib/source.ts(1 hunks)app/root.tsx(1 hunks)app/routes.ts(1 hunks)app/routes/home.tsx(1 hunks)content/docs/about.mdx(1 hunks)content/docs/changelog.mdx(2 hunks)content/docs/community/caddy.mdx(1 hunks)content/docs/community/kubernetes.mdx(6 hunks)content/docs/community/zitadel-oauth.mdx(1 hunks)content/docs/contributing.mdx(1 hunks)content/docs/getting-started.mdx(1 hunks)content/docs/guides/access-controls.mdx(1 hunks)content/docs/guides/advanced.mdx(1 hunks)content/docs/guides/github-app-oauth.mdx(1 hunks)content/docs/guides/github-oauth.mdx(1 hunks)content/docs/guides/google-oauth.mdx(1 hunks)content/docs/guides/ldap.mdx(1 hunks)content/docs/guides/nginx-proxy-manager.mdx(1 hunks)content/docs/guides/pocket-id.mdx(1 hunks)content/docs/guides/runtipi.mdx(1 hunks)content/docs/guides/totp.mdx(1 hunks)content/docs/guides/using-the-binary.mdx(1 hunks)content/docs/meta.json(1 hunks)content/docs/reference/cli.mdx(1 hunks)content/docs/reference/configuration.mdx(1 hunks)content/docs/reference/flow.mdx(1 hunks)content/docs/reference/headers.mdx(1 hunks)docs/about.md(0 hunks)docs/community/_category_.yml(0 hunks)docs/community/caddy.md(0 hunks)docs/community/zitadel-oauth.md(0 hunks)docs/contributing.md(0 hunks)docs/getting-started.md(0 hunks)docs/guides/_category_.yml(0 hunks)docs/guides/access-controls.md(0 hunks)docs/guides/advanced.md(0 hunks)docs/guides/github-app-oauth.md(0 hunks)docs/guides/github-oauth.md(0 hunks)docs/guides/google-oauth.md(0 hunks)docs/guides/ldap.md(0 hunks)docs/guides/nginx-proxy-manager.md(0 hunks)docs/guides/pocket-id.md(0 hunks)docs/guides/runtipi.md(0 hunks)docs/guides/totp.md(0 hunks)docs/guides/using-the-binary.md(0 hunks)docs/reference/_category_.yml(0 hunks)docs/reference/api.md(0 hunks)docs/reference/cli.md(0 hunks)docs/reference/configuration.md(0 hunks)docs/reference/flow.md(0 hunks)docs/reference/headers.md(0 hunks)docusaurus.config.ts(0 hunks)package.json(1 hunks)react-router.config.ts(1 hunks)sidebars.ts(0 hunks)source.config.ts(1 hunks)source.generated.ts(1 hunks)src/css/custom.css(0 hunks)src/pages/index.module.css(0 hunks)src/pages/index.tsx(0 hunks)tsconfig.json(1 hunks)vite.config.ts(1 hunks)
💤 Files with no reviewable changes (29)
- docs/guides/using-the-binary.md
- docs/reference/category.yml
- docs/guides/category.yml
- docs/guides/ldap.md
- docs/reference/api.md
- docs/reference/configuration.md
- docs/community/category.yml
- docs/guides/runtipi.md
- docs/guides/access-controls.md
- docs/community/caddy.md
- docs/guides/advanced.md
- docs/guides/google-oauth.md
- docs/reference/flow.md
- docs/guides/pocket-id.md
- docs/guides/nginx-proxy-manager.md
- docs/guides/totp.md
- docs/about.md
- sidebars.ts
- docs/guides/github-app-oauth.md
- docs/reference/headers.md
- docs/getting-started.md
- src/pages/index.module.css
- docs/contributing.md
- src/pages/index.tsx
- docs/guides/github-oauth.md
- docusaurus.config.ts
- docs/community/zitadel-oauth.md
- docs/reference/cli.md
- src/css/custom.css
🧰 Additional context used
🧬 Code graph analysis (24)
app/docs/search.ts (2)
app/lib/source.ts (1)
source(4-7)app/docs/page.tsx (1)
loader(16-25)
app/root.tsx (1)
app/routes/home.tsx (1)
meta(9-17)
app/lib/source.ts (3)
app/docs/page.tsx (1)
loader(16-25)source.generated.ts (2)
create(5-5)docs(7-21)source.config.ts (1)
docs(4-6)
source.config.ts (1)
source.generated.ts (1)
docs(7-21)
app/components/language-toggle.tsx (1)
app/components/ui/button.tsx (1)
buttonVariants(11-26)
app/components/layout/docs/client.tsx (4)
app/components/layout/home/navbar.tsx (1)
Navbar(27-54)app/components/sidebar.tsx (1)
SidebarCollapseTrigger(425-441)app/components/ui/button.tsx (1)
buttonVariants(11-26)app/components/search-toggle.tsx (1)
SearchToggle(15-43)
app/components/ui/toc-clerk.tsx (3)
app/components/ui/toc.tsx (1)
useTOCItems(12-14)app/components/ui/toc-thumb.tsx (1)
TocThumb(38-73)app/lib/merge-refs.ts (1)
mergeRefs(3-15)
app/components/layout/shared/index.tsx (1)
app/components/layout/docs/index.tsx (1)
LinkItemType(385-385)
app/components/layout/docs/page.tsx (2)
app/components/ui/toc.tsx (3)
TOCScrollArea(30-51)TOCItems(53-85)TOCProvider(16-28)app/components/ui/toc-clerk.tsx (1)
ClerkTOCItems(11-117)
app/components/layout/home/navbar.tsx (3)
app/components/layout/docs/client.tsx (1)
Navbar(12-28)app/components/ui/button.tsx (1)
buttonVariants(11-26)app/components/layout/shared/client.tsx (1)
BaseLinkItem(8-30)
app/lib/layout.shared.tsx (1)
app/components/layout/shared/index.tsx (1)
BaseLayoutProps(20-61)
app/components/sidebar.tsx (1)
app/lib/is-active.ts (1)
isActive(8-17)
app/components/layout/docs/index.tsx (10)
app/components/layout/shared/index.tsx (5)
BaseLayoutProps(20-61)getLinks(156-180)IconItemType(90-102)BaseLinkItem(182-182)LinkItemType(146-151)app/components/sidebar.tsx (15)
SidebarProps(37-62)SidebarComponents(457-461)SidebarViewport(237-254)SidebarPageTree(466-523)SidebarContentMobile(178-213)SidebarHeader(215-224)SidebarTrigger(408-423)SidebarContent(111-176)SidebarCollapseTrigger(425-441)Sidebar(89-109)SidebarFolder(294-315)SidebarFolderLink(337-370)SidebarFolderTrigger(317-335)SidebarFolderContent(372-406)SidebarItem(270-292)app/components/root-toggle.tsx (2)
Option(12-14)RootToggle(16-103)app/components/layout/shared/client.tsx (1)
BaseLinkItem(8-30)app/components/ui/button.tsx (1)
buttonVariants(11-26)app/components/language-toggle.tsx (2)
LanguageToggle(14-57)LanguageToggleText(59-68)app/components/theme-toggle.tsx (1)
ThemeToggle(27-88)app/components/github.tsx (1)
MdiGithub(3-18)app/components/search-toggle.tsx (2)
LargeSearchToggle(45-79)SearchToggle(15-43)app/components/layout/docs/client.tsx (3)
CollapsibleControl(54-81)Navbar(12-28)LayoutBody(30-52)
app/routes/home.tsx (5)
app/components/layout/home/index.tsx (1)
HomeLayout(40-76)app/lib/layout.shared.tsx (1)
baseOptions(3-9)app/components/card.tsx (1)
Card(8-24)app/components/github.tsx (1)
MdiGithub(3-18)app/components/discord.tsx (1)
IcBaselineDiscord(3-18)
app/components/layout/shared/client.tsx (2)
app/components/layout/shared/index.tsx (1)
BaseLinkType(72-81)app/lib/is-active.ts (1)
isActive(8-17)
app/components/ui/toc.tsx (2)
app/lib/merge-refs.ts (1)
mergeRefs(3-15)app/components/ui/toc-thumb.tsx (1)
TocThumb(38-73)
app/components/layout/home/index.tsx (8)
app/components/layout/shared/index.tsx (4)
BaseLayoutProps(20-61)NavOptions(5-18)getLinks(156-180)LinkItemType(146-151)app/components/layout/home/navbar.tsx (6)
Navbar(27-54)NavbarMenuLink(83-97)NavbarMenu(56-56)NavbarMenuTrigger(72-81)NavbarMenuContent(58-70)NavbarLink(118-136)app/components/search-toggle.tsx (2)
LargeSearchToggle(45-79)SearchToggle(15-43)app/components/theme-toggle.tsx (1)
ThemeToggle(27-88)app/components/github.tsx (1)
MdiGithub(3-18)app/components/language-toggle.tsx (2)
LanguageToggle(14-57)LanguageToggleText(59-68)app/components/layout/home/menu.tsx (4)
Menu(89-89)MenuTrigger(91-108)MenuContent(110-121)MenuLinkItem(34-87)app/components/ui/button.tsx (1)
buttonVariants(11-26)
source.generated.ts (1)
source.config.ts (1)
docs(4-6)
app/docs/page.tsx (5)
app/lib/source.ts (1)
source(4-7)source.generated.ts (1)
docs(7-21)app/components/layout/page.tsx (4)
DocsPage(113-203)DocsTitle(270-282)DocsDescription(250-266)DocsBody(233-246)app/components/mdx-components.tsx (1)
getMDXComponents(5-11)app/lib/layout.shared.tsx (1)
baseOptions(3-9)
app/components/search-toggle.tsx (1)
app/components/ui/button.tsx (2)
ButtonProps(28-28)buttonVariants(11-26)
app/components/layout/docs/page-client.tsx (3)
app/components/ui/toc.tsx (1)
useTOCItems(12-14)app/components/ui/collapsible.tsx (3)
CollapsibleTrigger(39-39)CollapsibleContent(39-39)Collapsible(39-39)app/lib/is-active.ts (1)
isActive(8-17)
app/components/layout/home/menu.tsx (3)
app/components/ui/button.tsx (1)
buttonVariants(11-26)app/components/layout/shared/index.tsx (2)
LinkItemType(146-151)BaseLinkItem(182-182)app/components/layout/shared/client.tsx (1)
BaseLinkItem(8-30)
app/components/layout/page.tsx (3)
app/components/layout/docs/page.tsx (13)
BreadcrumbProps(102-102)FooterProps(101-101)PageRoot(75-91)PageTOCPopover(98-98)PageTOCPopoverTrigger(99-99)PageTOCPopoverContent(100-100)PageTOCPopoverItems(46-55)PageArticle(57-69)PageBreadcrumb(94-94)PageLastUpdate(96-96)PageFooter(95-95)PageTOC(97-97)PageTOCTitle(20-33)app/components/layout/docs/page-client.tsx (9)
BreadcrumbProps(340-340)FooterProps(244-252)PageTOCPopover(163-219)PageTOCPopoverTrigger(39-91)PageTOCPopoverContent(151-161)PageBreadcrumb(342-392)PageLastUpdate(221-241)PageFooter(277-310)PageTOC(394-417)app/components/ui/button.tsx (1)
buttonVariants(11-26)
app/components/root-toggle.tsx (1)
app/lib/is-active.ts (2)
isTabActive(19-23)isActive(8-17)
🪛 ast-grep (0.39.5)
app/components/mdx/mermaid.tsx
[warning] 56-56: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
app/components/mdx/mermaid.tsx
[error] 57-57: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
app/routes/home.tsx
[error] 9-9: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
🪛 GitHub Actions: Deploy PR previews
react-router.config.ts
[error] 17-17: PLUGIN_ERROR: react-router:virtual-modules Could not load virtual:react-router/server-build: (0 , vite_ssr_import_0.glob)(...) is not a function or its return value is not async iterable
🔇 Additional comments (38)
app/components/ui/scroll-area.tsx (2)
5-21: LGTM!The
ScrollAreawrapper correctly forwards refs, uses appropriate type inference from Radix primitives, and includes sensible defaults (Corner and vertical ScrollBar).
23-36: LGTM!The
ScrollViewportwrapper correctly forwards refs and applies appropriate sizing/border-radius classes for typical viewport behavior.tsconfig.json (1)
2-25: Configuration looks consistent with the new tooling stack.The include set and compiler options line up with Vite + React Router requirements, and the rootDirs/path mapping cover the generated Fumadocs types cleanly.
app/root.tsx (4)
1-13: LGTM!The imports are well-organized and correctly leverage React Router v7 primitives along with FumaDocs providers for the documentation framework.
15-26: LGTM!The font preloading configuration is correct. The
crossOrigin: "anonymous"attribute on the gstatic preconnect is properly set for CORS compliance, and the Inter font is loaded with full variable weight support.
53-55: LGTM!The
Appcomponent correctly renders the React Router<Outlet />, which is the standard pattern for a root route component.
28-51: Verify provider nesting order. Confirm thatReactRouterProviderwrappingRootProviderfollows FumaDocs guidance. Unless there’s a specific need to load analytics in<head>, consider moving the<script defer src="https://analytics.doesmycode.work/script.js">to just before</body>for consistency.app/components/ui/toc.tsx (3)
30-51: LGTM!The
TOCScrollAreacomponent correctly handles ref merging and scroll container setup. The use ofPrimitive.ScrollProviderwith the container ref is appropriate.
53-85: LGTM!The
TOCItemscomponent properly handles the empty state and renders the TOC structure with appropriate styling and accessibility features.
87-101: LGTM!The
TOCItemcomponent correctly applies depth-based indentation and styling using the fumadocs primitives..vscode/settings.json (1)
2-11: LGTM!The spell checker configuration appropriately includes project-specific terms like "Tinyauth", "Runtipi", "TOTP", and "Traefik".
.prettierignore (1)
1-2: LGTM!The ignore pattern update from
.docusaurusto.react-routercorrectly reflects the framework migration described in the PR objectives.app/app.css (1)
1-3: LGTM!The CSS imports correctly set up Tailwind CSS alongside fumadocs-ui neutral and preset styles, establishing the styling foundation for the migrated documentation site.
.gitignore (1)
7-8: LGTM!The ignore pattern update from
.docusaurusto.react-routeraligns with the framework migration to React Router.app/lib/layout.shared.tsx (1)
1-9: LGTM!The
baseOptionsfunction provides a clean, typed configuration for the base layout with the application title. The implementation correctly usesBaseLayoutPropsfrom fumadocs-ui.content/docs/changelog.mdx (2)
1-4: LGTM!The addition of YAML front matter with title and description follows MDX best practices and aligns with the fumadocs documentation structure.
302-302: LGTM!The heading change from "Migration instructions" to "Migration guide" improves consistency with the heading used at line 203 and provides clearer terminology.
app/components/discord.tsx (1)
1-18: LGTM! Clean icon component implementation.The Discord icon component follows React best practices by forwarding SVG props and using
currentColorfor theme-friendly styling.app/lib/cn.ts (1)
1-1: LGTM! Standard Tailwind utility alias.This re-export creates a convenient
cnalias fortailwind-merge, a common pattern in Tailwind-based projects for merging class names without conflicts.vite.config.ts (1)
1-9: LGTM! Clean Vite configuration.The plugin composition is correct, with MDX processing first, followed by Tailwind CSS integration, React Router dev tooling, and TypeScript path resolution.
app/components/ui/button.tsx (1)
1-28: LGTM! Well-structured button variants with intentional aliasing.The implementation correctly uses class-variance-authority for type-safe variant composition. The dual
variantandcolorkeys mapping to the same variants object is intentional for FumaDocs compatibility, as noted in the inline comment.app/lib/merge-refs.ts (1)
1-15: LGTM! Correct ref merging implementation.The utility correctly handles both function refs and object refs with proper type safety, following standard React patterns for merging multiple refs into a single callback.
source.config.ts (1)
1-12: LGTM! Clean FumaDocs configuration.The configuration correctly sets up the docs collection and enables Mermaid diagram support through the MDX pipeline. This aligns with the generated source file that imports from this config.
app/components/ui/collapsible.tsx (1)
1-39: LGTM! Proper Radix UI wrapper with hydration-safe animations.The implementation correctly wraps Radix UI primitives with the
'use client'directive for React Router's SSR. Themountedstate pattern at lines 14-18 prevents animation class hydration mismatches by deferring animation classes until after client-side mount, which is a best practice for SSR frameworks.package.json (2)
6-9: LGTM! Clean migration to React Router tooling.The scripts correctly reflect the new React Router + Vite workflow, including proper type generation with
react-router typegenbefore TypeScript checking.
27-29: React 19 compatibility confirmed All React-based dependencies (React Router, Radix UI, next-themes, fumadocs-ui, lucide-react) declare peerDependencies including React 19.app/components/card.tsx (1)
1-24: LGTM! Clean component structure.The Card component is well-structured with clear prop types and appropriate hover effects. The implementation follows React best practices with proper TypeScript typing.
app/components/layout/page.tsx (1)
233-295: LGTM! Well-structured page composition utilities.The
DocsBody,DocsDescription,DocsTitle, andwithArticleexports provide clean, reusable primitives for documentation page composition. The forwardRef pattern with displayName is correctly implemented, and the conditional rendering in DocsDescription appropriately handles undefined content.app/components/ui/popover.tsx (1)
30-30: Confirm PopoverClose alias
PopoverPrimitive.PopoverClose may not exist—Radix UI’s Popover primitive exportsClose, notPopoverClose. Verify in node_modules/@radix-ui/react-popover and update the alias toPopoverPrimitive.Closeif needed.content/docs/guides/runtipi.mdx (2)
18-22: LGTM!The image tag is correctly formatted with width and alt attributes.
34-39: Verify fallback forward auth URL stability: the hardcodedtinyauth_migrated-tinyauth-1container name is Docker Compose–specific and may differ across environments. Add a note instructing users to confirm their actual container name (e.g. viadocker ps) or to set a stable Compose project name so the URL remains valid.content/docs/guides/github-oauth.mdx (1)
1-63: LGTM!The guide is clear, well-structured, and includes appropriate security warnings and configuration tips. The callback URL format and environment variable names are consistent with Tinyauth's conventions.
app/routes.ts (1)
1-7: LGTM – all referenced route modules exist and the configuration satisfies RouteConfig.content/docs/guides/access-controls.mdx (1)
1-145: Comprehensive and well-structured documentation.The access controls guide is clear, well-organized, and provides practical examples. The use of callouts effectively highlights security considerations, tips, and important information. The YAML examples are correct and the regex patterns in path ACLs are properly explained.
app/components/mdx-components.tsx (1)
1-11: Clean component composition pattern.The
getMDXComponentshelper correctly merges the default MDX components with your custom Mermaid component and allows for optional overrides. The spread order ensures proper precedence: defaults → Mermaid override → user-provided components.content/docs/contributing.mdx (1)
1-66: Clear contributing guide with practical setup instructions.The contributing documentation is well-structured and provides a straightforward path for new contributors. The Docker-based workflow with domain configuration examples is helpful.
Verify that Golang v1.23.2 is still the minimum required version, as this might drift over time. If the project uses a
go.modfile with a specific Go version directive, ensure this documentation stays aligned.react-router.config.ts (1)
17-19: CI Node.js version supports fs.promises.glob — no code change neededVerified Node.js v24.3.0 and
fs.promises.globis a function; the async iterable API is available. If CI still errors, update the pipeline’s Node version to ≥ v20.11.0.app/components/root-toggle.tsx (1)
9-24: Drop the private SidebarTab importThis file also depends on
fumadocs-ui/utils/get-sidebar-tabs, which is an internal path that can change without notice. Define the shape you need locally (url, icon, title, description, unlisted, urls) and extend that instead.-import type { SidebarTab } from 'fumadocs-ui/utils/get-sidebar-tabs'; - -export interface Option extends SidebarTab { +type SidebarTabLike = { + url: string; + title: string; + description?: string; + icon?: ReactNode; + unlisted?: boolean; + urls?: Set<string>; +}; + +export interface Option extends SidebarTabLike { props?: ComponentProps<'a'>; }[ suggest_essential_refactor ]
| { | ||
| "aliases": { | ||
| "uiDir": "./components/ui", | ||
| "componentsDir": "./components", | ||
| "blockDir": "./components", | ||
| "cssDir": "./styles", | ||
| "libDir": "./lib" | ||
| }, | ||
| "baseDir": "", | ||
| "commands": {} | ||
| } No newline at end of file |
There was a problem hiding this comment.
🧩 Analysis chain
Verify the cssDir alias path
The cssDir alias points to "./styles", but the actual CSS file (app/app.css) is located in the root of the app/ directory. If this configuration is used by tooling to locate CSS files, this mismatch could cause issues.
Run the following script to check the actual CSS file locations:
🏁 Script executed:
#!/bin/bash
# Description: Verify CSS file locations vs. cssDir alias
# Check if ./styles directory exists
fd -t d -d 1 '^styles$' app/
# List all CSS files in the app directory
fd -e css -t f . app/Length of output: 11
Update cssDir alias to match actual CSS location
In app/cli.json, change "cssDir": "./styles" to "cssDir": "./" so it points to app/app.css.
🤖 Prompt for AI Agents
In app/cli.json around lines 1 to 11 the cssDir alias currently points to
"./styles" but the actual CSS file is app/app.css; update the cssDir value to
"./" so the alias resolves to the project root where app.css lives, saving the
file after the change.
| export function BaseLinkItem({ | ||
| ref, | ||
| item, | ||
| ...props | ||
| }: Omit<ComponentProps<'a'>, 'href'> & { item: BaseLinkType }) { | ||
| const pathname = usePathname(); | ||
| const activeType = item.active ?? 'url'; | ||
| const active = | ||
| activeType !== 'none' && | ||
| isActive(item.url, pathname, activeType === 'nested-url'); | ||
|
|
||
| return ( | ||
| <Link | ||
| ref={ref} | ||
| href={item.url} | ||
| external={item.external} | ||
| {...props} | ||
| data-active={active} | ||
| > | ||
| {props.children} | ||
| </Link> | ||
| ); |
There was a problem hiding this comment.
Wrap BaseLinkItem in forwardRef so refs actually work.
React doesn’t pass ref through props on function components. As written, ref is always undefined, and consumers will get “Function components cannot be given refs” when trying to use one. Convert the component to forwardRef (or drop ref support entirely).
-'use client';
-import type { ComponentProps } from 'react';
+'use client';
+import { forwardRef, type ComponentProps } from 'react';
@@
-export function BaseLinkItem({
- ref,
- item,
- ...props
-}: Omit<ComponentProps<'a'>, 'href'> & { item: BaseLinkType }) {
+type BaseLinkItemProps = Omit<ComponentProps<'a'>, 'href'> & {
+ item: BaseLinkType;
+};
+
+export const BaseLinkItem = forwardRef<HTMLAnchorElement, BaseLinkItemProps>(
+ ({ item, ...props }, ref) => {
@@
- return (
- <Link
- ref={ref}
- href={item.url}
- external={item.external}
- {...props}
- data-active={active}
- >
- {props.children}
- </Link>
- );
-}
+ return (
+ <Link
+ ref={ref}
+ href={item.url}
+ external={item.external}
+ {...props}
+ data-active={active}
+ >
+ {props.children}
+ </Link>
+ );
+ },
+);
+
+BaseLinkItem.displayName = 'BaseLinkItem';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function BaseLinkItem({ | |
| ref, | |
| item, | |
| ...props | |
| }: Omit<ComponentProps<'a'>, 'href'> & { item: BaseLinkType }) { | |
| const pathname = usePathname(); | |
| const activeType = item.active ?? 'url'; | |
| const active = | |
| activeType !== 'none' && | |
| isActive(item.url, pathname, activeType === 'nested-url'); | |
| return ( | |
| <Link | |
| ref={ref} | |
| href={item.url} | |
| external={item.external} | |
| {...props} | |
| data-active={active} | |
| > | |
| {props.children} | |
| </Link> | |
| ); | |
| 'use client'; | |
| import { forwardRef, type ComponentProps } from 'react'; | |
| type BaseLinkItemProps = Omit<ComponentProps<'a'>, 'href'> & { | |
| item: BaseLinkType; | |
| }; | |
| export const BaseLinkItem = forwardRef<HTMLAnchorElement, BaseLinkItemProps>( | |
| ({ item, ...props }, ref) => { | |
| const pathname = usePathname(); | |
| const activeType = item.active ?? 'url'; | |
| const active = | |
| activeType !== 'none' && | |
| isActive(item.url, pathname, activeType === 'nested-url'); | |
| return ( | |
| <Link | |
| ref={ref} | |
| href={item.url} | |
| external={item.external} | |
| {...props} | |
| data-active={active} | |
| > | |
| {props.children} | |
| </Link> | |
| ); | |
| }, | |
| ); | |
| BaseLinkItem.displayName = 'BaseLinkItem'; |
🤖 Prompt for AI Agents
In app/components/layout/shared/client.tsx around lines 8 to 29, the function
component accepts a `ref` prop directly which React won't forward; wrap the
component with React.forwardRef so refs work: remove `ref` from the explicit
props, create a forwarded component using forwardRef<HTMLAnchorElement,
Omit<ComponentProps<'a'>, 'href'> & { item: BaseLinkType }>, accept (props, ref)
in the forwardRef callback, pass that ref into the <Link ref={ref} .../>
element, and export the forwarded result; also import forwardRef (or React) at
the top and adjust the prop typing accordingly.
| type ComponentProps, | ||
| createContext, | ||
| type FC, | ||
| Fragment, | ||
| type ReactNode, | ||
| useContext, | ||
| useMemo, | ||
| useRef, | ||
| useState, | ||
| } from "react"; |
There was a problem hiding this comment.
TS fixes: React namespace types + style casting
- Using
React.Dispatch/React.SetStateActionwithout importing theReactnamespace breaks under the automatic JSX runtime. Import the types directly. - Avoid
as objectfor inlinestyle; useCSSPropertiesfor type safety.
import {
type ComponentProps,
createContext,
type FC,
Fragment,
type ReactNode,
useContext,
useMemo,
useRef,
useState,
+ type CSSProperties,
+ type Dispatch,
+ type SetStateAction,
} from "react";
@@
const FolderContext = createContext<{
open: boolean;
- setOpen: React.Dispatch<React.SetStateAction<boolean>>;
+ setOpen: Dispatch<SetStateAction<boolean>>;
} | null>(null);
@@
style={
{
...props.style,
"--fd-sidebar-offset": hover
? "calc(var(--spacing) * 2)"
: "calc(16px - 100%)",
"--fd-sidebar-margin": collapsed ? "0.5rem" : "0px",
"--fd-sidebar-top": `calc(var(--fd-banner-height) + var(--fd-nav-height) + var(--fd-sidebar-margin))`,
width: collapsed
? "var(--fd-sidebar-width)"
: "calc(var(--spacing) + var(--fd-sidebar-width) + var(--fd-layout-offset))",
- } as object
+ } as CSSProperties
}
@@
<ScrollViewport
className="p-4 overscroll-contain"
style={
{
"--sidebar-item-offset": "calc(var(--spacing) * 2)",
maskImage:
"linear-gradient(to bottom, transparent, white 12px, white calc(100% - 12px), transparent)",
- } as object
+ } as CSSProperties
}
>
@@
style={
{
"--sidebar-item-offset": `calc(var(--spacing) * ${(level + 1) * 3})`,
...props.style,
- } as object
+ } as CSSProperties
}Also applies to: 84-87, 135-147, 242-248, 386-391
🤖 Prompt for AI Agents
In app/components/sidebar.tsx around lines 5-14 (and likewise apply to lines
84-87, 135-147, 242-248, 386-391), replace usages of
React.Dispatch/React.SetStateAction by importing the types directly (e.g., add
"import type { Dispatch, SetStateAction, CSSProperties } from 'react'") and
remove any "as object" style casts—use the CSSProperties type for inline style
props (e.g., annotate style variables/props as CSSProperties or type the style
prop as CSSProperties) and update any function/prop type signatures to use the
imported Dispatch/SetStateAction types so the file compiles under the automatic
JSX runtime.
| <Presence present={open}> | ||
| {({ present }) => ( | ||
| <aside | ||
| id="nd-sidebar-mobile" | ||
| {...props} | ||
| data-state={state} | ||
| className={cn( | ||
| "fixed text-[15px] flex flex-col shadow-lg border-s end-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out", | ||
| !present && "invisible", | ||
| className, | ||
| )} | ||
| > | ||
| {children} | ||
| </aside> | ||
| )} | ||
| </Presence> | ||
| </> |
There was a problem hiding this comment.
Fix: Presence is not a render‑prop; remove function child
@radix-ui/react-presence doesn’t pass { present } via render props. Rendering a function here will throw at runtime. Render the <aside> directly as the child.
- <Presence present={open}>
- {({ present }) => (
- <aside
- id="nd-sidebar-mobile"
- {...props}
- data-state={state}
- className={cn(
- "fixed text-[15px] flex flex-col shadow-lg border-s end-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out",
- !present && "invisible",
- className,
- )}
- >
- {children}
- </aside>
- )}
- </Presence>
+ <Presence present={open}>
+ <aside
+ id="nd-sidebar-mobile"
+ {...props}
+ data-state={state}
+ className={cn(
+ "fixed text-[15px] flex flex-col shadow-lg border-s end-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out",
+ className,
+ )}
+ >
+ {children}
+ </aside>
+ </Presence>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Presence present={open}> | |
| {({ present }) => ( | |
| <aside | |
| id="nd-sidebar-mobile" | |
| {...props} | |
| data-state={state} | |
| className={cn( | |
| "fixed text-[15px] flex flex-col shadow-lg border-s end-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out", | |
| !present && "invisible", | |
| className, | |
| )} | |
| > | |
| {children} | |
| </aside> | |
| )} | |
| </Presence> | |
| </> | |
| <Presence present={open}> | |
| <aside | |
| id="nd-sidebar-mobile" | |
| {...props} | |
| data-state={state} | |
| className={cn( | |
| "fixed text-[15px] flex flex-col shadow-lg border-s end-0 inset-y-0 w-[85%] max-w-[380px] z-40 bg-fd-background data-[state=open]:animate-fd-sidebar-in data-[state=closed]:animate-fd-sidebar-out", | |
| className, | |
| )} | |
| > | |
| {children} | |
| </aside> | |
| </Presence> | |
| </> |
🤖 Prompt for AI Agents
In app/components/sidebar.tsx around lines 195 to 211, the Presence component is
being used with a function child expecting a render-prop ({ present }) but
@radix-ui/react-presence does not provide render props; change the child to
render the <aside> element directly (remove the function wrapper and use
Presence as a simple wrapper), and move any conditional logic that depended on {
present } to use the open prop/state or Presence’s semantics (e.g., keep using
the existing open/state variable to set data-state and conditional classes like
!open && "invisible"); ensure props, className and children are passed to the
aside as before.
| export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { | ||
| let message = "Oops!"; | ||
| let details = "An unexpected error occurred."; | ||
| let stack: string | undefined; | ||
|
|
||
| if (isRouteErrorResponse(error)) { | ||
| message = error.status === 404 ? "404" : "Error"; | ||
| details = | ||
| error.status === 404 | ||
| ? "The requested page could not be found." | ||
| : error.statusText || details; | ||
| } else if (import.meta.env.DEV && error && error instanceof Error) { | ||
| details = error.message; | ||
| stack = error.stack; | ||
| } | ||
|
|
||
| return ( | ||
| <main className="px-4 gap-6 flex flex-col items-center justify-center min-h-screen"> | ||
| <div className="flex flex-col gap-3 text-center"> | ||
| <h1 className="text-4xl font-black">{message}</h1> | ||
| <p>{details}</p> | ||
| <Link | ||
| to="/" | ||
| className="text-sm bg-fd-card text-fd-primary border border-fd-border rounded-md px-4 py-2.5 hover:opacity-80 hover:scale-105 transition-transform delay-100" | ||
| > | ||
| Go home | ||
| </Link> | ||
| </div> | ||
| {stack && ( | ||
| <pre className="w-full p-4 overflow-x-auto"> | ||
| <code>{stack}</code> | ||
| </pre> | ||
| )} | ||
| </main> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Strengthen error handling and type safety.
The ErrorBoundary implementation has a few concerns:
-
Type narrowing issue (lines 68-71): After the
isRouteErrorResponsecheck fails, the code assumes the error is anErrorinstance. However, errors can be any type (string, number, null, object, etc.). The current logic doesn't handle non-Error throwables. -
Stack trace exposure (lines 85-89): While the stack trace rendering is gated by
import.meta.env.DEV, if thestackvariable is populated through other means in production, sensitive information could leak. Consider adding an additional runtime check. -
Redundant null check (line 68): The
error &&check is unnecessary sinceErrorBoundaryPropsguaranteeserroris defined.
Apply this diff to improve error handling:
- } else if (import.meta.env.DEV && error && error instanceof Error) {
+ } else if (error instanceof Error) {
details = error.message;
- stack = error.stack;
+ stack = import.meta.env.DEV ? error.stack : undefined;
+ } else if (import.meta.env.DEV) {
+ // Handle non-Error throwables in development
+ details = String(error);
}This ensures:
- Stack traces are only captured in DEV mode
- Non-Error throwables are handled gracefully
- Type narrowing is clearer
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { | |
| let message = "Oops!"; | |
| let details = "An unexpected error occurred."; | |
| let stack: string | undefined; | |
| if (isRouteErrorResponse(error)) { | |
| message = error.status === 404 ? "404" : "Error"; | |
| details = | |
| error.status === 404 | |
| ? "The requested page could not be found." | |
| : error.statusText || details; | |
| } else if (import.meta.env.DEV && error && error instanceof Error) { | |
| details = error.message; | |
| stack = error.stack; | |
| } | |
| return ( | |
| <main className="px-4 gap-6 flex flex-col items-center justify-center min-h-screen"> | |
| <div className="flex flex-col gap-3 text-center"> | |
| <h1 className="text-4xl font-black">{message}</h1> | |
| <p>{details}</p> | |
| <Link | |
| to="/" | |
| className="text-sm bg-fd-card text-fd-primary border border-fd-border rounded-md px-4 py-2.5 hover:opacity-80 hover:scale-105 transition-transform delay-100" | |
| > | |
| Go home | |
| </Link> | |
| </div> | |
| {stack && ( | |
| <pre className="w-full p-4 overflow-x-auto"> | |
| <code>{stack}</code> | |
| </pre> | |
| )} | |
| </main> | |
| ); | |
| } | |
| export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { | |
| let message = "Oops!"; | |
| let details = "An unexpected error occurred."; | |
| let stack: string | undefined; | |
| if (isRouteErrorResponse(error)) { | |
| message = error.status === 404 ? "404" : "Error"; | |
| details = | |
| error.status === 404 | |
| ? "The requested page could not be found." | |
| : error.statusText || details; | |
| } else if (error instanceof Error) { | |
| details = error.message; | |
| stack = import.meta.env.DEV ? error.stack : undefined; | |
| } else if (import.meta.env.DEV) { | |
| // Handle non-Error throwables in development | |
| details = String(error); | |
| } | |
| return ( | |
| <main className="px-4 gap-6 flex flex-col items-center justify-center min-h-screen"> | |
| <div className="flex flex-col gap-3 text-center"> | |
| <h1 className="text-4xl font-black">{message}</h1> | |
| <p>{details}</p> | |
| <Link | |
| to="/" | |
| className="text-sm bg-fd-card text-fd-primary border border-fd-border rounded-md px-4 py-2.5 hover:opacity-80 hover:scale-105 transition-transform delay-100" | |
| > | |
| Go home | |
| </Link> | |
| </div> | |
| {stack && ( | |
| <pre className="w-full p-4 overflow-x-auto"> | |
| <code>{stack}</code> | |
| </pre> | |
| )} | |
| </main> | |
| ); | |
| } |
🤖 Prompt for AI Agents
In app/root.tsx around lines 57 to 92, the ErrorBoundary currently assumes
non-Route errors are Error instances, redundantly checks for error presence, and
could expose stack traces outside DEV; change the logic to (1) remove the
unnecessary `error &&` check, (2) explicitly narrow the non-Route error by
testing `error instanceof Error` (or via a small isError helper) and only then
assign details = error.message and stack = error.stack inside the
import.meta.env.DEV block, and (3) ensure stack is only ever populated when
import.meta.env.DEV is true so the render guard for the <pre> block can simply
check stack exists without an extra runtime mode check; also add a fallback for
non-Error throwables (e.g., coerce to string with String(error) for details) so
all throwables are handled gracefully.
| title="OAuth and LDAP support" | ||
| description="With Tinyauth you can you can easily login to your apps using your favorite OAuth providers or by using a centralized LDAP server." | ||
| /> |
There was a problem hiding this comment.
Fix duplicate wording in the “OAuth and LDAP support” copy.
The string currently reads “you can you can” and should be tightened up.
- description="With Tinyauth you can you can easily login to your apps using your favorite OAuth providers or by using a centralized LDAP server."
+ description="With Tinyauth you can easily log in to your apps using your favorite OAuth providers or a centralized LDAP server."📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| title="OAuth and LDAP support" | |
| description="With Tinyauth you can you can easily login to your apps using your favorite OAuth providers or by using a centralized LDAP server." | |
| /> | |
| title="OAuth and LDAP support" | |
| description="With Tinyauth you can easily log in to your apps using your favorite OAuth providers or a centralized LDAP server." | |
| /> |
🤖 Prompt for AI Agents
In app/routes/home.tsx around lines 61 to 63, the description string contains a
duplicated phrase "you can you can"; remove the duplicate so the sentence reads
cleanly (e.g., "With Tinyauth you can easily login to your apps using your
favorite OAuth providers or by using a centralized LDAP server.") and save the
file.
| - APP_URL=http://auth.example.com | ||
| - USERS=your-username-password-hash |
There was a problem hiding this comment.
Use an HTTPS APP_URL when Caddy terminates TLS.
With Caddy handling TLS, clients reach Tinyauth over https://. Keeping APP_URL as http://auth.example.com will mislead deployers into issuing insecure redirects and non-Secure cookies, breaking logins and weakening security. Please change the example to https://auth.example.com.
🤖 Prompt for AI Agents
In content/docs/community/caddy.mdx around lines 63 to 64, the example APP_URL
uses http://auth.example.com which is incorrect when Caddy terminates TLS;
update the example to use https://auth.example.com so generated redirects and
cookies are issued as Secure and match the actual client-facing scheme, ensuring
APP_URL is https and any related docs/text reflect the HTTPS URL.
| - PROVIDERS_ZITADEL_SCOPES=openid profile email preferred_username groups | ||
| - PROVIDERS_ZITADEL_AUTH_URL=https://zitadel.example.com/oauth/v2/authorize | ||
| - PROVIDERS_ZITADEL_TOKEN_URL=https://zitadel.example.com/oauth/v2/token | ||
| - PROVIDERS_ZITADEL_USER_INFO_URL=https://zitadel.example.com/oidc/v1/userinfo |
There was a problem hiding this comment.
Invalid Zitadel scopes will break OAuth handshake
preferred_username and groups are claims, not valid Zitadel scopes. Zitadel rejects unknown scopes with invalid_scope, so Tinyauth will never complete the login flow as written.
- - PROVIDERS_ZITADEL_SCOPES=openid profile email preferred_username groups
+ - PROVIDERS_ZITADEL_SCOPES=openid profile emailIf you need organization roles or custom claims, request the proper urn:zitadel:iam:project:roles:... scopes documented by Zitadel instead of these claim names.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - PROVIDERS_ZITADEL_SCOPES=openid profile email preferred_username groups | |
| - PROVIDERS_ZITADEL_AUTH_URL=https://zitadel.example.com/oauth/v2/authorize | |
| - PROVIDERS_ZITADEL_TOKEN_URL=https://zitadel.example.com/oauth/v2/token | |
| - PROVIDERS_ZITADEL_USER_INFO_URL=https://zitadel.example.com/oidc/v1/userinfo | |
| - PROVIDERS_ZITADEL_SCOPES=openid profile email | |
| - PROVIDERS_ZITADEL_AUTH_URL=https://zitadel.example.com/oauth/v2/authorize | |
| - PROVIDERS_ZITADEL_TOKEN_URL=https://zitadel.example.com/oauth/v2/token | |
| - PROVIDERS_ZITADEL_USER_INFO_URL=https://zitadel.example.com/oidc/v1/userinfo |
🤖 Prompt for AI Agents
In content/docs/community/zitadel-oauth.mdx around lines 44 to 47, the example
environment variable PROVIDERS_ZITADEL_SCOPES includes claim names
("preferred_username" and "groups") which are invalid Zitadel scopes and will
cause an invalid_scope error; remove those claim names from
PROVIDERS_ZITADEL_SCOPES (keep only valid scopes such as openid profile email)
and, if organization roles or custom claims are required, replace them with the
appropriate Zitadel URN scopes (e.g. urn:zitadel:iam:project:roles:...) as
documented by Zitadel.
| description: Use the GitHub Apps OAuth screen for authenticating to Tinyauth. | ||
| --- | ||
|
|
||
| Tinyauth also supports GitHub Apps for authentication instead of OAuth Apps. GitHub Apps allow more control over permissions and are slightly more complex to set up. For simpler setups, the [OAuth Apps](/docs/guides/github-oauth.md) guide is recommended. |
There was a problem hiding this comment.
Fix the internal link extension.
Line 6 references /docs/guides/github-oauth.md, but the repository uses .mdx files. Update to /docs/guides/github-oauth.mdx or use a route-based link without extension (e.g., /docs/guides/github-oauth) if the routing layer strips extensions.
Apply this diff:
-Tinyauth also supports GitHub Apps for authentication instead of OAuth Apps. GitHub Apps allow more control over permissions and are slightly more complex to set up. For simpler setups, the [OAuth Apps](/docs/guides/github-oauth.md) guide is recommended.
+Tinyauth also supports GitHub Apps for authentication instead of OAuth Apps. GitHub Apps allow more control over permissions and are slightly more complex to set up. For simpler setups, the [OAuth Apps](/docs/guides/github-oauth) guide is recommended.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Tinyauth also supports GitHub Apps for authentication instead of OAuth Apps. GitHub Apps allow more control over permissions and are slightly more complex to set up. For simpler setups, the [OAuth Apps](/docs/guides/github-oauth.md) guide is recommended. | |
| Tinyauth also supports GitHub Apps for authentication instead of OAuth Apps. GitHub Apps allow more control over permissions and are slightly more complex to set up. For simpler setups, the [OAuth Apps](/docs/guides/github-oauth) guide is recommended. |
🤖 Prompt for AI Agents
In content/docs/guides/github-app-oauth.mdx around line 6, the internal link
points to /docs/guides/github-oauth.md but the repo uses .mdx (or route-based
links); update the link target to /docs/guides/github-oauth.mdx or to the
extensionless route /docs/guides/github-oauth so it resolves correctly in the
site routing.
| - LDAP_BIND_PASSWORD=your-observer-user-password | ||
| - LDAP_BASE_DN=dc=example,dc=com | ||
| - LDAP_SEARCH_FILTER=(uid=%s) | ||
| - LDAP_INSECURE=true |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Document the security implications of LDAP_INSECURE=true.
Line 44 sets LDAP_INSECURE=true in the example without explaining the security trade-off. This flag disables TLS certificate validation, making the connection vulnerable to man-in-the-middle attacks.
Add a callout after the configuration block:
```yaml
services:
tinyauth:
environment:
- LDAP_ADDRESS=ldap://my-lldap-server:3890
- LDAP_BIND_DN=uid=your-observer-user,ou=people,dc=example,dc=com
- LDAP_BIND_PASSWORD=your-observer-user-password
- LDAP_BASE_DN=dc=example,dc=com
- LDAP_SEARCH_FILTER=(uid=%s)
- LDAP_INSECURE=true+
LDAP_INSECURE=truedisables TLS certificate verification and should only be used for testing or in trusted networks. For production, useldaps://with valid certificates and omit this flag or set it tofalse.
+
<details>
<summary>🤖 Prompt for AI Agents</summary>
content/docs/guides/ldap.mdx around line 44: the example sets LDAP_INSECURE=true
without warning; add a warning Callout immediately after the configuration block
that states this flag disables TLS certificate verification and is only for
testing or trusted networks, instruct readers to use ldaps:// with valid certs
and omit or set LDAP_INSECURE to false in production, and ensure the MDX callout
syntax is valid and placed right after the YAML block.
</details>
<!-- This is an auto-generated comment by CodeRabbit -->
|
/preview |
2 similar comments
|
/preview |
|
/preview |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
package.json (1)
2-2: Rename the package to match the project.The scaffold name
example-react-routeris still here; please align it with the actual docs project name.Apply this diff:
- "name": "example-react-router", + "name": "tinyauth-docs",
🧹 Nitpick comments (1)
app/components/search.tsx (1)
1-1: Pipeline failure: Sourcemap resolution error.The build pipeline reports a sourcemap error: "Can't resolve original location of error." This is likely a build configuration issue rather than a code problem, but it may affect debugging experience.
Consider checking:
- Vite/bundler sourcemap configuration
- Whether all dependencies provide valid sourcemaps
- Build output for any warnings about sourcemap generation
This does not block functionality but may make debugging more difficult in production.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
app/components/search.tsx(1 hunks)app/root.tsx(1 hunks)package.json(1 hunks)react-router.config.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- react-router.config.ts
- app/root.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
app/components/search.tsx (1)
source.generated.ts (1)
create(5-5)
🪛 GitHub Actions: Deploy PR previews
app/components/search.tsx
[error] 1-1: Error when using sourcemap for reporting an error: Can't resolve original location of error.
🔇 Additional comments (3)
app/components/search.tsx (3)
1-15: LGTM!The imports are correct for a FumaDocs search dialog with Orama integration. The
"use client"directive is appropriate for this interactive component.
25-46: LGTM!The component structure and hook integration follow FumaDocs patterns correctly. The
useDocsSearchhook is properly configured for static/client-side search with Orama.
47-47: Verifyquery.dataempty-state sentinel
The checkquery.data !== "empty"assumes the hook returns"empty"as a sentinel, which is uncommon. Confirm the actual type/empty value returned byuseDocsSearchinfumadocs-core/search/client. If it never returns"empty", replace with a standard nullish or length check, for example:<SearchDialogList items={query.data ?? null} />
| function initOrama() { | ||
| return create({ | ||
| schema: { _: "string" }, | ||
| // https://docs.orama.com/docs/orama-js/supported-languages | ||
| language: "english", | ||
| }); | ||
| } |
There was a problem hiding this comment.
Adjust the schema to match FumaDocs document structure.
The minimal schema { _: "string" } will not work correctly with FumaDocs search documents, which typically contain title, content, url, and other structured fields. The useDocsSearch hook expects documents with these standard fields.
Apply this diff to use a proper schema:
function initOrama() {
return create({
- schema: { _: "string" },
+ schema: {
+ title: "string",
+ content: "string",
+ url: "string",
+ section: "string",
+ },
// https://docs.orama.com/docs/orama-js/supported-languages
language: "english",
});
}Based on learnings and FumaDocs documentation patterns.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function initOrama() { | |
| return create({ | |
| schema: { _: "string" }, | |
| // https://docs.orama.com/docs/orama-js/supported-languages | |
| language: "english", | |
| }); | |
| } | |
| function initOrama() { | |
| return create({ | |
| schema: { | |
| title: "string", | |
| content: "string", | |
| url: "string", | |
| section: "string", | |
| }, | |
| // https://docs.orama.com/docs/orama-js/supported-languages | |
| language: "english", | |
| }); | |
| } |
🤖 Prompt for AI Agents
In app/components/search.tsx around lines 17 to 23, the Orama schema currently
uses a minimal placeholder { _: "string" } which doesn't match FumaDocs
documents; replace it with a schema that defines the real document shape (for
example fields like title: "string", content: "string", url: "string", summary:
"string", tags: "string[]", and any metadata/date fields as "string" or
appropriate types) and keep language: "english"; ensure the schema keys match
what useDocsSearch and stored documents expect so indexing/searching works
correctly.
|
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.github/workflows/deploy.yml(2 hunks)react-router.config.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- react-router.config.ts
| uses: actions/cache@v4 | ||
| with: | ||
| path: ~/.bun/install/cache | ||
| key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lockb') }} | ||
| key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lock') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-bun-web- |
There was a problem hiding this comment.
Restore cache key to hash the actual Bun lockfile.
Switching the cache key to hashFiles('bun.lock') breaks cache busting because Bun still generates bun.lockb by default, so the hash is now empty and the job falls back to a static key. That means dependency updates will keep reusing stale cache entries. Please keep hashing bun.lockb, or include both patterns if you’re intentionally migrating to the text lockfile.
Apply this diff (or equivalent) to fix it:
- key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lock') }}
+ key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lockb') }}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lockb') }} | |
| key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun-web- | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lockb') }} | |
| restore-keys: | | |
| ${{ runner.os }}-bun-web- |
🤖 Prompt for AI Agents
.github/workflows/deploy.yml around lines 26 to 31: the cache key currently
hashes bun.lock which is empty when Bun generates bun.lockb by default, causing
cache misses and stale reuse; change the key to hash the binary lockfile
(bun.lockb) or include both files so hashing falls back correctly (e.g., use
hashFiles('bun.lockb') or hashFiles('bun.lock','bun.lockb')) and update any
restore-keys accordingly so dependency changes produce new cache keys.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
content/docs/breaking-updates/3-to-4.mdx (1)
128-135: Tighten wording in migration example.The sentence “You will have to change it too:” should be “change it to:” so the instruction reads naturally.
| | Environment | Flag | Description | Default | Required | | ||
| | ------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------ | -------- | | ||
| | `PROVIDERS_[PROVIDER]_CLIENT_ID` | `--providers-[provider]-client-id` | The client ID for the OAuth client. | `` | yes | | ||
| | `PROVIDERS_[PROVIDER]_CLIENT_SECRET` | `--providers-[provider]-client-secret` | The client secret for the OAuth client. | `` | yes | | ||
| | `PROVIDERS_[PROVIDER]_CLIENT_SECRET_FILE` | `--providers-[provider]-client-secret-file` | A path to a file containing the client secret for the OAuth client. | `` | no | | ||
| | `PROVIDERS_[PROVIDER]_SCOPES` | `--providers-[provider]-scopes` | The scopes needed for the OAuth provider. | `` | yes | | ||
| | `PROVIDERS_[PROVIDER]_AUTH_URL` | `--providers-[provider]-auth-url` | The authentication URL for the OAuth provider. | `` | yes | | ||
| | `PROVIDERS_[PROVIDER]_TOKEN_URL` | `--providers-[provider]-token-url` | The token URL for the OAuth provider. | `` | yes | | ||
| | `PROVIDERS_[PROVIDER]_USER_INFO_URL` | `--providers-[provider]-user-info-url` | The user information URL for the OAuth provider. | `` | yes | | ||
| | `PROVIDERS_[PROVIDER]_INSECURE_SKIP_VERIFY` | `--providers-[provider]-insecure-skip-verify` | Skip SSL certificate check for provider. | `false` | no | | ||
| | `PROVIDERS_[PROVIDER]_NAME` | `--providers-[provider]-name` | The name of the OAuth provider (for the UI button). | Provider ID with first letter capitalized. | no | | ||
|
|
There was a problem hiding this comment.
Clarify whether scopes are actually required for built-in providers.
The table marks PROVIDERS_[PROVIDER]_SCOPES (and other fields) as “Required: yes,” but the callout immediately below says Google/GitHub IDs auto-populate data like scopes. Please reconcile these statements—either adjust the required column or clarify that the “yes” applies only to fully custom providers so users aren’t misled.
Also applies to: 79-81
🤖 Prompt for AI Agents
In content/docs/breaking-updates/3-to-4.mdx around lines 53 to 64 (and also
apply the same change at lines 79–81), the table currently marks
PROVIDERS_[PROVIDER]_SCOPES and other provider fields as “Required: yes” which
conflicts with the following callout that built-in Google/GitHub auto-populate
some values; update the table and adjacent text to reconcile this by either (a)
change the Required column for fields that are optional for built-in providers
to “yes (custom only)” or “no (built-in auto-populates)”, or (b) add a short
footnote on the table header clarifying “Required: yes applies to custom
providers; built-in providers may auto-populate these values”, and mirror that
clarification in the callout so readers understand which fields are mandatory
only for custom providers.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
app/routes/discord.ts (1)
3-5: LGTM! Consider removing unnecessaryasynckeyword.The loader correctly uses the
throw redirect()pattern for React Router v7 data routers. The unconditional redirect to the Discord invite is appropriate for this use case.The
asynckeyword is unnecessary since the function doesn't useawait:-export async function loader() { +export function loader() { throw redirect("https://discord.gg/eHzVaCzRRd"); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/routes.ts(1 hunks)app/routes/discord.ts(1 hunks)
🔇 Additional comments (1)
app/routes.ts (1)
1-8: LGTM! Route configuration follows React Router v7 best practices.The route definitions correctly use the
@react-router/dev/routesAPI with properindex()androute()helper functions. The configuration includes:
- Index route for the home page
- Discord redirect route (leveraging the loader from
app/routes/discord.ts)- Wildcard route for docs sections
- API search route
The
satisfies RouteConfigassertion ensures type safety while maintaining the required structure.Based on learnings.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
content/docs/community/zitadel-oauth.mdx (1)
44-44: Fix invalid Zitadel scopes
preferred_usernameandgroupsare OpenID claims, not requestable Zitadel scopes. Zitadel will reject this configuration withinvalid_scope, so the login flow fails. Drop those entries (or replace them with the documentedurn:zitadel:iam:project:roles:...scopes if roles are required).- - PROVIDERS_ZITADEL_SCOPES=openid profile email preferred_username groups + - PROVIDERS_ZITADEL_SCOPES=openid profile emailcontent/docs/guides/using-the-binary.mdx (1)
33-38: Replace brittle.envexport/unset snippet with a POSIX-safe pattern.
export $(grep … | xargs -d '\n')and the pairedunsetpipeline fall apart as soon as a value contains spaces/quotes, and-d '\n'is GNU-only. Readers with perfectly valid.envfiles will hit cryptic shell errors. Please switch to a portable approach (e.g.set -a; . ./.env; set +a) and drop the fragileunsetpipeline in favor of explicitunset KEYguidance.-```sh -export $(grep -v '^#' .env | xargs -d '\n') -``` - -<Callout type="info"> - To unset the environment variables for security purposes, use: `unset $(grep - -v '^#' .env | sed -E 's/(.*)=.*/\1/' | xargs)`. -</Callout> +```sh +set -a +. ./.env +set +a +``` + +<Callout type="info"> + Unset only the variables you exported, e.g. `unset TINYAUTH_SECRET`. +</Callout>content/docs/breaking-updates/3-to-4.mdx (1)
55-81: Resolve the “Required” column contradiction for built-in providers.Rows for scopes/auth/token/user-info still say “Required: yes,” yet the callout explains Google/GitHub auto-fill those values so users only need ID/secret. Please align the table (and, if needed, the callout) so it’s unambiguous that these fields are only mandatory for fully custom providers—right now the guidance conflicts. Suggested patch:
-| `PROVIDERS_[PROVIDER]_SCOPES` | `--providers-[provider]-scopes` | The scopes needed for the OAuth provider. | `` | yes | -| `PROVIDERS_[PROVIDER]_AUTH_URL` | `--providers-[provider]-auth-url` | The authentication URL for the OAuth provider. | `` | yes | -| `PROVIDERS_[PROVIDER]_TOKEN_URL` | `--providers-[provider]-token-url` | The token URL for the OAuth provider. | `` | yes | -| `PROVIDERS_[PROVIDER]_USER_INFO_URL` | `--providers-[provider]-user-info-url` | The user information URL for the OAuth provider. | `` | yes | +| `PROVIDERS_[PROVIDER]_SCOPES` | `--providers-[provider]-scopes` | The scopes needed for the OAuth provider. | `` | yes (custom) | +| `PROVIDERS_[PROVIDER]_AUTH_URL` | `--providers-[provider]-auth-url` | The authentication URL for the OAuth provider. | `` | yes (custom) | +| `PROVIDERS_[PROVIDER]_TOKEN_URL` | `--providers-[provider]-token-url` | The token URL for the OAuth provider. | `` | yes (custom) | +| `PROVIDERS_[PROVIDER]_USER_INFO_URL` | `--providers-[provider]-user-info-url` | The user information URL for the OAuth provider. | `` | yes (custom) |…and add a short note clarifying built-in IDs auto-fill the defaults.
🧹 Nitpick comments (1)
content/docs/guides/advanced.mdx (1)
45-57: Scope the trusted IP range more narrowly.Recommending
172.16.0.0/12trusts a very large slice of private address space. Narrow it to the actual networks that sit in front of Traefik (e.g., the precise Docker bridge or host subnet) so you don’t accidentally accept spoofed forwarded headers from other internal actors. Please adjust the example and mention tailoring the CIDR to the environment.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
content/docs/breaking-updates/3-to-4.mdx(1 hunks)content/docs/changelog.mdx(2 hunks)content/docs/community/zitadel-oauth.mdx(1 hunks)content/docs/contributing.mdx(1 hunks)content/docs/guides/access-controls.mdx(1 hunks)content/docs/guides/advanced.mdx(1 hunks)content/docs/guides/github-app-oauth.mdx(1 hunks)content/docs/guides/github-oauth.mdx(1 hunks)content/docs/guides/google-oauth.mdx(1 hunks)content/docs/guides/pocket-id.mdx(1 hunks)content/docs/guides/using-the-binary.mdx(1 hunks)content/docs/reference/cli.mdx(1 hunks)content/docs/reference/configuration.mdx(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- content/docs/guides/pocket-id.mdx
- content/docs/reference/configuration.mdx
🚧 Files skipped from review as they are similar to previous changes (4)
- content/docs/guides/github-app-oauth.mdx
- content/docs/guides/google-oauth.mdx
- content/docs/reference/cli.mdx
- content/docs/contributing.mdx
🔇 Additional comments (1)
content/docs/guides/access-controls.mdx (1)
1-145: Comprehensive addition looks solid. Clear structure, accurate callouts, and thorough coverage of label usage. Nicely done.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
content/docs/breaking-updates/3-to-4.mdx (1)
55-63: Clarify “Required” column for auto-populated providers.The table still marks these fields as strictly required, yet the callout right below says Google/GitHub auto-fill them. Please reconcile so readers don’t assume they must supply values that are generated for built-ins.
Consider explicit wording such as “yes (custom providers)”:
-| `PROVIDERS_[PROVIDER]_SCOPES` | `--providers-[provider]-scopes` | The scopes needed for the OAuth provider. | `` | yes | -| `PROVIDERS_[PROVIDER]_AUTH_URL` | `--providers-[provider]-auth-url` | The authentication URL for the OAuth provider. | `` | yes | -| `PROVIDERS_[PROVIDER]_TOKEN_URL` | `--providers-[provider]-token-url` | The token URL for the OAuth provider. | `` | yes | -| `PROVIDERS_[PROVIDER]_USER_INFO_URL` | `--providers-[provider]-user-info-url` | The user information URL for the OAuth provider. | `` | yes | +| `PROVIDERS_[PROVIDER]_SCOPES` | `--providers-[provider]-scopes` | The scopes needed for the OAuth provider. | `` | yes (custom providers) | +| `PROVIDERS_[PROVIDER]_AUTH_URL` | `--providers-[provider]-auth-url` | The authentication URL for the OAuth provider. | `` | yes (custom providers) | +| `PROVIDERS_[PROVIDER]_TOKEN_URL` | `--providers-[provider]-token-url` | The token URL for the OAuth provider. | `` | yes (custom providers) | +| `PROVIDERS_[PROVIDER]_USER_INFO_URL` | `--providers-[provider]-user-info-url` | The user information URL for the OAuth provider. | `` | yes (custom providers) |content/docs/changelog.mdx (1)
8-11: Fix migration guide link slug.Fumadocs strips file extensions, so this .mdx link 404s. Point it at the extensionless route.
- [documentation](/docs/breaking-updates/3-to-4.mdx). + [documentation](/docs/breaking-updates/3-to-4).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
app/docs/search.ts(1 hunks)app/routes.ts(1 hunks)app/routes/discord.tsx(1 hunks)content/docs/breaking-updates/3-to-4.mdx(1 hunks)content/docs/changelog.mdx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/docs/search.ts
🔇 Additional comments (1)
app/routes.ts (1)
1-8: LGTM!The route configuration follows React Router v7 best practices correctly:
- Uses the
@react-router/dev/routesAPI for route definitions- Employs TypeScript's
satisfiesoperator for type safety- Route structure is clean and follows standard patterns (index route, named routes, catch-all docs route, and API resource route)
| export default function Discord() { | ||
| const discordUrl = "https://discord.gg/eHzVaCzRRd"; | ||
| return ( | ||
| <html> | ||
| <head> | ||
| <meta httpEquiv="refresh" content={`0; url=${discordUrl}`} /> | ||
| <title>Redirecting...</title> | ||
| <meta charSet="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| </head> | ||
| <body> | ||
| <div className="p-4"> | ||
| <p className="mb-1">Redirecting to the Discord server...</p> | ||
| <a | ||
| className="text-fd-info underline hover:opacity-80" | ||
| href={discordUrl} | ||
| > | ||
| Redirect now | ||
| </a> | ||
| </div> | ||
| </body> | ||
| </html> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Replace full HTML document with a loader redirect.
Returning a full <html> document from a React Router v7 route component bypasses the app shell, layout components, and React Router context. This breaks the routing pipeline and prevents Tailwind classes from being applied correctly.
The recommended pattern in React Router v7 is to use a loader that returns a redirect Response for server-side redirects:
+import { redirect } from "react-router";
+
+export function loader() {
+ return redirect("https://discord.gg/eHzVaCzRRd");
+}
+
export default function Discord() {
- const discordUrl = "https://discord.gg/eHzVaCzRRd";
- return (
- <html>
- <head>
- <meta httpEquiv="refresh" content={`0; url=${discordUrl}`} />
- <title>Redirecting...</title>
- <meta charSet="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- </head>
- <body>
- <div className="p-4">
- <p className="mb-1">Redirecting to the Discord server...</p>
- <a
- className="text-fd-info underline hover:opacity-80"
- href={discordUrl}
- >
- Redirect now
- </a>
- </div>
- </body>
- </html>
- );
+ return null; // This component won't render due to loader redirect
}Alternatively, if you need a client-side redirect with a fallback UI:
import { useEffect } from "react";
import { useNavigate } from "react-router";
export default function Discord() {
const navigate = useNavigate();
const discordUrl = "https://discord.gg/eHzVaCzRRd";
useEffect(() => {
window.location.href = discordUrl;
}, [discordUrl]);
return (
<div className="p-4">
<p className="mb-1">Redirecting to the Discord server...</p>
<a
className="text-fd-info underline hover:opacity-80"
href={discordUrl}
>
Redirect now
</a>
</div>
);
}Based on learnings: React Router v7 consolidates loader/redirect patterns for Data Mode routing.
🤖 Prompt for AI Agents
In app/routes/discord.tsx around lines 1-24 you're returning a full <html>
document which bypasses the app shell and React Router context; replace it with
a route component that either (a) uses a route loader to perform a server-side
redirect by returning a Redirect/Response to the Discord URL, or (b) performs a
client-side redirect: remove the <html>/<head>/<body> wrapper and export a
normal React component that renders only the small fallback UI (div with text
and link) and triggers window.location.href (or useNavigate/useEffect) to
navigate to the discordUrl on mount; update the file to implement one of these
two patterns and remove the full document markup so Tailwind and layouts work
correctly.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
.github/workflows/deploy.yml (1)
29-31: Restore cache key to hash the actual Bun lockfile (bun.lockb), or include both.Hashing
bun.lockbreaks cache busting when Bun generatesbun.lockbby default, leading to stale caches. Use the binary lockfile (or both) to ensure cache invalidation on dependency changes.Apply one of these diffs:
Option A (binary lockfile only, current Bun default):
- key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lock') }} + key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lockb') }}Option B (future‑proof: hash both text and binary lockfiles):
- key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lock') }} + key: ${{ runner.os }}-bun-web-${{ hashFiles('bun.lockb', 'bun.lock') }}Nice to have: also include the Bun version in the key to avoid cross‑version cache reuse.
🧹 Nitpick comments (1)
.github/workflows/deploy.yml (1)
18-21: Consider dropping setup-node if not required.If the build/test steps run entirely via Bun,
actions/setup-node@v5is unnecessary and slows CI. Remove it unless a step actually needs Node 20.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.github/workflows/deploy.yml(1 hunks)
🔇 Additional comments (2)
.github/workflows/deploy.yml (2)
34-34: LGTM on frozen lockfile; verify a lockfile is committed.
bun install --frozen-lockfileis good for reproducibility. Ensure the repo contains the expected lockfile (bun.lockbunless you’ve switched to text) so this doesn’t fail unexpectedly.
42-42: Verify artifact path matches the new build output.Confirm your build produces
build/clientwith anindex.htmlat its root. If your tool outputs elsewhere (e.g.,distorbuild), update the path accordingly to avoid deploying an empty site.
Summary by CodeRabbit
New Features
Documentation
Chores