doccupine 0.0.66 → 0.0.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -109,6 +109,18 @@ ${hasSections
109
109
 
110
110
  return (
111
111
  <html lang="en">
112
+ <head>
113
+ {/* Prevents dark-mode FOUC on Safari/Firefox. These browsers don't support
114
+ Sec-CH-Prefers-Color-Scheme (handled by middleware for Chrome), so on
115
+ a first visit this blocking script detects prefers-color-scheme, sets
116
+ the theme cookie, and hides the body until router.refresh() re-renders
117
+ with the correct theme (see ClientThemeProvider). */}
118
+ <script
119
+ dangerouslySetInnerHTML={{
120
+ __html: \`(function(){try{var c=document.cookie.split(";").find(function(s){return s.trim().startsWith("theme=")});if(!c){var d=window.matchMedia&&window.matchMedia("(prefers-color-scheme:dark)").matches;document.cookie="theme="+(d?"dark":"light")+";path=/;max-age=31536000;SameSite=Lax";if(d){var s=document.createElement("style");s.id="__theme-init";s.textContent="html{background:#000!important;color-scheme:dark}body{visibility:hidden}";document.head.appendChild(s)}}}catch(e){}})();\`,
121
+ }}
122
+ />
123
+ </head>
112
124
  <body className={font.className}>
113
125
  <StyledComponentsRegistry>
114
126
  <CherryThemeProvider theme={theme} themeDark={themeDark}>
@@ -156,6 +168,18 @@ ${hasSections
156
168
 
157
169
  return (
158
170
  <html lang="en">
171
+ <head>
172
+ {/* Prevents dark-mode FOUC on Safari/Firefox. These browsers don't support
173
+ Sec-CH-Prefers-Color-Scheme (handled by middleware for Chrome), so on
174
+ a first visit this blocking script detects prefers-color-scheme, sets
175
+ the theme cookie, and hides the body until router.refresh() re-renders
176
+ with the correct theme (see ClientThemeProvider). */}
177
+ <script
178
+ dangerouslySetInnerHTML={{
179
+ __html: \`(function(){try{var c=document.cookie.split(";").find(function(s){return s.trim().startsWith("theme=")});if(!c){var d=window.matchMedia&&window.matchMedia("(prefers-color-scheme:dark)").matches;document.cookie="theme="+(d?"dark":"light")+";path=/;max-age=31536000;SameSite=Lax";if(d){var s=document.createElement("style");s.id="__theme-init";s.textContent="html{background:#000!important;color-scheme:dark}body{visibility:hidden}";document.head.appendChild(s)}}}catch(e){}})();\`,
180
+ }}
181
+ />
182
+ </head>
159
183
  <body className={font.className}>
160
184
  <StyledComponentsRegistry>
161
185
  <CherryThemeProvider theme={theme} themeDark={themeDark}>
@@ -1 +1 @@
1
- export declare const clientThemeProviderTemplate = "\"use client\";\nimport React, { useEffect, useMemo, useState, useContext } from \"react\";\nimport { ThemeProvider as StyledThemeProvider } from \"styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { useRouter } from \"next/navigation\";\nimport { GlobalStyles } from \"@/components/layout/GlobalStyles\";\n\ntype ThemeOverrideContextValue = {\n theme: Theme;\n setTheme: (t: Theme | null) => void;\n};\n\nconst ThemeOverrideContext =\n React.createContext<ThemeOverrideContextValue | null>(null);\n\nfunction useThemeOverride() {\n const ctx = useContext(ThemeOverrideContext);\n if (!ctx) {\n throw new Error(\"useThemeOverride must be used within ClientThemeProvider\");\n }\n return ctx;\n}\n\nfunction ClientThemeProvider({\n children,\n theme,\n}: {\n children: React.ReactNode;\n theme: Theme;\n}) {\n const router = useRouter();\n const [overrideTheme, setOverrideTheme] = useState<Theme | null>(null);\n useEffect(() => {\n try {\n const cookie = document.cookie\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(\"theme=\"));\n const cookieValue = cookie ? cookie.split(\"=\")[1] : undefined;\n if (!cookieValue) {\n const prefersDark =\n window.matchMedia &&\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n const shouldBe = prefersDark ? \"dark\" : \"light\";\n fetch(\"/api/theme\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ theme: shouldBe }),\n })\n .then(() => {\n router.refresh();\n })\n .catch(() => {\n console.error(\"Error setting theme\");\n });\n }\n } catch {}\n }, [router]);\n\n const effectiveTheme = useMemo(\n () => overrideTheme ?? theme,\n [overrideTheme, theme],\n );\n return (\n <ThemeOverrideContext.Provider\n value={{ theme: effectiveTheme, setTheme: setOverrideTheme }}\n >\n <StyledThemeProvider theme={effectiveTheme}>\n <GlobalStyles />\n {children}\n </StyledThemeProvider>\n </ThemeOverrideContext.Provider>\n );\n}\n\nexport { ClientThemeProvider, useThemeOverride };\n";
1
+ export declare const clientThemeProviderTemplate = "\"use client\";\nimport React, { useEffect, useMemo, useState, useContext } from \"react\";\nimport { ThemeProvider as StyledThemeProvider } from \"styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { useRouter } from \"next/navigation\";\nimport { GlobalStyles } from \"@/components/layout/GlobalStyles\";\n\ntype ThemeOverrideContextValue = {\n theme: Theme;\n setTheme: (t: Theme | null) => void;\n};\n\nconst ThemeOverrideContext =\n React.createContext<ThemeOverrideContextValue | null>(null);\n\nfunction useThemeOverride() {\n const ctx = useContext(ThemeOverrideContext);\n if (!ctx) {\n throw new Error(\"useThemeOverride must be used within ClientThemeProvider\");\n }\n return ctx;\n}\n\nfunction ClientThemeProvider({\n children,\n theme,\n}: {\n children: React.ReactNode;\n theme: Theme;\n}) {\n const router = useRouter();\n const [overrideTheme, setOverrideTheme] = useState<Theme | null>(null);\n useEffect(() => {\n try {\n const cookie = document.cookie\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(\"theme=\"));\n const cookieValue = cookie ? cookie.split(\"=\")[1] : undefined;\n if (!cookieValue) {\n // Fallback: blocking script should have set this, but handle edge cases\n const prefersDark =\n window.matchMedia &&\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n const shouldBe = prefersDark ? \"dark\" : \"light\";\n document.cookie = `theme=${shouldBe};path=/;max-age=31536000;SameSite=Lax`;\n router.refresh();\n } else if (theme.isDark !== (cookieValue === \"dark\")) {\n // Server-rendered theme doesn't match cookie (e.g., cookie was set\n // by the blocking script after SSR already committed to light theme)\n router.refresh();\n } else {\n // Theme matches cookie - remove the flash-prevention style injected\n // by the blocking script so styled-components takes over\n const el = document.getElementById(\"__theme-init\");\n if (el) el.remove();\n }\n } catch {}\n }, [router, theme.isDark]);\n\n const effectiveTheme = useMemo(\n () => overrideTheme ?? theme,\n [overrideTheme, theme],\n );\n return (\n <ThemeOverrideContext.Provider\n value={{ theme: effectiveTheme, setTheme: setOverrideTheme }}\n >\n <StyledThemeProvider theme={effectiveTheme}>\n <GlobalStyles />\n {children}\n </StyledThemeProvider>\n </ThemeOverrideContext.Provider>\n );\n}\n\nexport { ClientThemeProvider, useThemeOverride };\n";
@@ -38,24 +38,25 @@ function ClientThemeProvider({
38
38
  .find((c) => c.startsWith("theme="));
39
39
  const cookieValue = cookie ? cookie.split("=")[1] : undefined;
40
40
  if (!cookieValue) {
41
+ // Fallback: blocking script should have set this, but handle edge cases
41
42
  const prefersDark =
42
43
  window.matchMedia &&
43
44
  window.matchMedia("(prefers-color-scheme: dark)").matches;
44
45
  const shouldBe = prefersDark ? "dark" : "light";
45
- fetch("/api/theme", {
46
- method: "POST",
47
- headers: { "Content-Type": "application/json" },
48
- body: JSON.stringify({ theme: shouldBe }),
49
- })
50
- .then(() => {
51
- router.refresh();
52
- })
53
- .catch(() => {
54
- console.error("Error setting theme");
55
- });
46
+ document.cookie = \`theme=\${shouldBe};path=/;max-age=31536000;SameSite=Lax\`;
47
+ router.refresh();
48
+ } else if (theme.isDark !== (cookieValue === "dark")) {
49
+ // Server-rendered theme doesn't match cookie (e.g., cookie was set
50
+ // by the blocking script after SSR already committed to light theme)
51
+ router.refresh();
52
+ } else {
53
+ // Theme matches cookie - remove the flash-prevention style injected
54
+ // by the blocking script so styled-components takes over
55
+ const el = document.getElementById("__theme-init");
56
+ if (el) el.remove();
56
57
  }
57
58
  } catch {}
58
- }, [router]);
59
+ }, [router, theme.isDark]);
59
60
 
60
61
  const effectiveTheme = useMemo(
61
62
  () => overrideTheme ?? theme,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.66",
3
+ "version": "0.0.67",
4
4
  "description": "Free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {