From 74b4ae30f9adc77972e1e3d4080e87771fb6da3e Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 12 Sep 2026 12:15:08 +0100 Subject: [PATCH 1/3] fix(react-widget): drop the stranded 'use client' directive Parcel's library build concatenates modules, so the directive at the top of DocsGPTWidget.tsx landed a thousand lines into dist/module.js and dist/main.js. webpack rejects a 'use client' that is not the first expression in the file, which broke every Next.js consumer of docsgpt-react: x The "use client" directive must be placed before other expressions. 1168 | 'use client'; The directive could not do its job from inside a bundle anyway. A React Server Components boundary has to be declared by the importing module, so consumers wrap the widget in their own client component instead. --- extensions/react-widget/src/components/DocsGPTWidget.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index 7af67307..94f09fd2 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -1,4 +1,3 @@ -'use client'; import React, { useRef } from 'react'; import DOMPurify from 'dompurify'; import styled, { keyframes, css } from 'styled-components'; From 3711426c83c1d8a2d0215c8a56afa2a2b3635042 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 12 Sep 2026 12:15:08 +0100 Subject: [PATCH 2/3] feat(docs): mount the DocsGPT chat widget Adds a floating "Ask the docs" bubble to every docs page. docsgpt-react was already a dependency but nothing imported it, so no widget rendered. The widget reads window and navigator on mount, so it is lazy-loaded with ssr: false from a client wrapper. Its light/dark theme follows the site toggle via next-themes, promoted here from a transitive dependency to a declared one. The key comes from NEXT_PUBLIC_DOCSGPT_API_KEY and the host from NEXT_PUBLIC_DOCSGPT_API_HOST. With no key set the wrapper renders nothing rather than falling through to the demo key the package defaults to. Bumping docsgpt-react to ^0.7.0 also drops a duplicate React. The old ^0.6.3 pin depended on React 18 and npm installed a nested 18.3.1 beside the app's 19.2.8, which would have thrown an invalid hook call on first render. --- docs/app/layout.jsx | 2 + docs/components/DocsGPTChatWidget.jsx | 40 ++++++++++++++ docs/package-lock.json | 75 ++++----------------------- docs/package.json | 3 +- 4 files changed, 55 insertions(+), 65 deletions(-) create mode 100644 docs/components/DocsGPTChatWidget.jsx diff --git a/docs/app/layout.jsx b/docs/app/layout.jsx index 85911381..040d1781 100644 --- a/docs/app/layout.jsx +++ b/docs/app/layout.jsx @@ -5,6 +5,7 @@ import { getPageMap } from 'nextra/page-map'; import { Footer, Layout, Navbar } from 'nextra-theme-docs'; import 'nextra-theme-docs/style.css'; +import { DocsGPTChatWidget } from '../components/DocsGPTChatWidget'; import CuteLogo from '../public/cute-docsgpt.png'; import themeConfig from '../theme.config'; @@ -79,6 +80,7 @@ export default async function RootLayout({ children }) { > {children} + diff --git a/docs/components/DocsGPTChatWidget.jsx b/docs/components/DocsGPTChatWidget.jsx new file mode 100644 index 00000000..9f688ac0 --- /dev/null +++ b/docs/components/DocsGPTChatWidget.jsx @@ -0,0 +1,40 @@ +'use client'; + +import dynamic from 'next/dynamic'; +import { useTheme } from 'next-themes'; +import { useEffect, useState } from 'react'; + +// The widget reads window/navigator on mount and ships its own styled-components +// trees, so it must never be prerendered. +const DocsGPTWidget = dynamic( + () => import('docsgpt-react').then((mod) => mod.DocsGPTWidget), + { ssr: false }, +); + +const apiHost = + process.env.NEXT_PUBLIC_DOCSGPT_API_HOST || 'https://gptcloud.arc53.com'; +const apiKey = process.env.NEXT_PUBLIC_DOCSGPT_API_KEY; + +export function DocsGPTChatWidget() { + const { resolvedTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + useEffect(() => setMounted(true), []); + + // No key means no widget. Falling through would silently use the demo key + // baked into the package default. + if (!apiKey || !mounted) return null; + + return ( + + ); +} diff --git a/docs/package-lock.json b/docs/package-lock.json index 5ec785aa..ae0e7b0a 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -7,8 +7,9 @@ "license": "MIT", "dependencies": { "@vercel/analytics": "^2.0.1", - "docsgpt-react": "^0.6.3", + "docsgpt-react": "^0.7.0", "next": "^16.3.0", + "next-themes": "^0.4.6", "nextra": "^4.6.1", "nextra-theme-docs": "^4.6.1", "react": "^19.2.8", @@ -311,13 +312,6 @@ "node": ">=6.9.0" } }, - "node_modules/@bpmn-io/snarkdown": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@bpmn-io/snarkdown/-/snarkdown-2.2.0.tgz", - "integrity": "sha512-bVD7FIoaBDZeCJkMRgnBPDeptPlto87wt2qaCjf5t8iLaevDmTPaREd6FpBEGsHlUdHFFZWRk4qAoEC5So2M0Q==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT" - }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", @@ -4389,13 +4383,12 @@ } }, "node_modules/docsgpt-react": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/docsgpt-react/-/docsgpt-react-0.6.3.tgz", - "integrity": "sha512-sG2sb129O76ux2KbQJLEJ/rD1CCaO3vAKC4fN8/Ks5GaATC9lbavZy/PH75Y/q0bOMsPkh/Kdq6xSfGTfTIAuA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/docsgpt-react/-/docsgpt-react-0.7.0.tgz", + "integrity": "sha512-/noRUe3+5pudINS+nGYq4aN5NwNI9LC+teU8g23r8QxKubk8/xybFt6HS0ZK9JnCnMC/2GbnNL4kSP0IGw0vaA==", "license": "Apache-2.0", "dependencies": { "@babel/plugin-transform-flow-strip-types": "^7.23.3", - "@bpmn-io/snarkdown": "^2.2.0", "@parcel/resolver-glob": "^2.16.4", "@parcel/transformer-svg-react": "^2.16.4", "@parcel/transformer-typescript-tsc": "^2.16.4", @@ -4404,47 +4397,13 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "dompurify": "^3.1.5", - "flow-bin": "^0.306.0", + "flow-bin": "^0.326.0", "markdown-it": "^14.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^19.2.5", + "react-dom": "^19.2.5", "styled-components": "^6.1.8" } }, - "node_modules/docsgpt-react/node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/docsgpt-react/node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/docsgpt-react/node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "node_modules/dompurify": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", @@ -4765,9 +4724,9 @@ } }, "node_modules/flow-bin": { - "version": "0.306.1", - "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.306.1.tgz", - "integrity": "sha512-xcLHE4dGHbsFio7/55YcFgeWttMEvpoC0T9FHj6xDB7frXvi+IQTWuBQtXpWrHPFflaUGF4jpQ1l6Rnlsev+Gw==", + "version": "0.326.0", + "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.326.0.tgz", + "integrity": "sha512-2WUYp7px5GeXLX+c2iFAUbr7093ta2c7bjox0pVTWaA6guMP91WFq3mkGKKzbKCIjqgDJepmMF5UPLafaJGE/A==", "license": "MIT", "bin": { "flow": "cli.js" @@ -5544,18 +5503,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", diff --git a/docs/package.json b/docs/package.json index 9830fe61..be4c32e4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -8,8 +8,9 @@ "license": "MIT", "dependencies": { "@vercel/analytics": "^2.0.1", - "docsgpt-react": "^0.6.3", + "docsgpt-react": "^0.7.0", "next": "^16.3.0", + "next-themes": "^0.4.6", "nextra": "^4.6.1", "nextra-theme-docs": "^4.6.1", "react": "^19.2.8", From 2b77d1994e1fcd2411e4ce839f44f8f714ce9e7f Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 12 Sep 2026 16:17:02 +0100 Subject: [PATCH 3/3] feat(docs): use the docs agent key by default The widget now ships with the docs agent's embed token, so the site works without any environment configuration. NEXT_PUBLIC_DOCSGPT_API_KEY still overrides it for staging. The key is public either way. NEXT_PUBLIC_* is inlined at build time, so it lands in the client bundle whichever way it is supplied, and every documented DocsGPT embed carries its key in the page. Abuse is bounded by the agent's daily request and token limits, enforced by check_usage on the answer and stream routes. --- docs/components/DocsGPTChatWidget.jsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/components/DocsGPTChatWidget.jsx b/docs/components/DocsGPTChatWidget.jsx index 9f688ac0..785f461b 100644 --- a/docs/components/DocsGPTChatWidget.jsx +++ b/docs/components/DocsGPTChatWidget.jsx @@ -13,7 +13,15 @@ const DocsGPTWidget = dynamic( const apiHost = process.env.NEXT_PUBLIC_DOCSGPT_API_HOST || 'https://gptcloud.arc53.com'; -const apiKey = process.env.NEXT_PUBLIC_DOCSGPT_API_KEY; + +// Public embed token for the docs agent, the same way every documented +// DocsGPT embed carries one. It reaches the browser either way, since +// NEXT_PUBLIC_* is inlined at build time. Abuse is capped by the agent's +// daily request and token limits, so rotate the agent key if it needs +// revoking. NEXT_PUBLIC_DOCSGPT_API_KEY overrides it for staging. +const apiKey = + process.env.NEXT_PUBLIC_DOCSGPT_API_KEY || + '0e714713-1bc6-4aae-a595-aa0b3bf317b3'; export function DocsGPTChatWidget() { const { resolvedTheme } = useTheme(); @@ -21,9 +29,9 @@ export function DocsGPTChatWidget() { useEffect(() => setMounted(true), []); - // No key means no widget. Falling through would silently use the demo key - // baked into the package default. - if (!apiKey || !mounted) return null; + // Hold off until next-themes has resolved, so the widget does not flash + // the wrong theme on first paint. + if (!mounted) return null; return (