From 13059b843c462721bd6f9d35c9ccd77b14ebc8cf Mon Sep 17 00:00:00 2001 From: Peter White <1788320+peterwhite@users.noreply.github.com> Date: Thu, 13 Aug 2026 08:57:13 +0200 Subject: [PATCH 01/15] Fix Docs Embed being stuck on the Search tab (RND-12435) (#4496) --- .changeset/embed-search-tab-navigation.md | 5 +++++ .../gitbook/src/components/Search/useSearchController.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/embed-search-tab-navigation.md diff --git a/.changeset/embed-search-tab-navigation.md b/.changeset/embed-search-tab-navigation.md new file mode 100644 index 000000000..4dd9395b5 --- /dev/null +++ b/.changeset/embed-search-tab-navigation.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Fix switching from the Search tab to the Docs or Assistant tab in the embed doing nothing. diff --git a/packages/gitbook/src/components/Search/useSearchController.tsx b/packages/gitbook/src/components/Search/useSearchController.tsx index 65e2250af..ff032288f 100644 --- a/packages/gitbook/src/components/Search/useSearchController.tsx +++ b/packages/gitbook/src/components/Search/useSearchController.tsx @@ -176,7 +176,7 @@ export function useSearchController( const onClose = React.useCallback( async (to?: string) => { - setSearchState((prev) => { + await setSearchState((prev) => { if (!prev) return null; if (prev.query !== null) { From ea19801cfaa7e625ed4ede5931c6d9208fd89db0 Mon Sep 17 00:00:00 2001 From: Tomek Date: Thu, 13 Aug 2026 13:17:26 +0200 Subject: [PATCH 02/15] feat: add greeting customization to AI chat (#4483) --- .changeset/tired-mails-fall.md | 5 +++++ bun.lock | 4 ++-- package.json | 2 +- packages/gitbook/src/components/AI/useAI.tsx | 2 +- packages/gitbook/src/components/AIChat/AIChat.tsx | 3 ++- packages/gitbook/src/components/SiteLayout/SiteLayout.tsx | 3 +++ 6 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 .changeset/tired-mails-fall.md diff --git a/.changeset/tired-mails-fall.md b/.changeset/tired-mails-fall.md new file mode 100644 index 000000000..e0867d728 --- /dev/null +++ b/.changeset/tired-mails-fall.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Support localized custom AI Assistant greeting subtitles. diff --git a/bun.lock b/bun.lock index ca5f7f67f..b6f2bd2f7 100644 --- a/bun.lock +++ b/bun.lock @@ -360,7 +360,7 @@ "react-dom": "catalog:", }, "catalog": { - "@gitbook/api": "0.192.0", + "@gitbook/api": "0.195.0", "@scalar/api-client-react": "^1.3.46", "@tsconfig/node20": "^20.1.6", "@tsconfig/strictest": "^2.0.6", @@ -760,7 +760,7 @@ "@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@7.2.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.2.0" } }, "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q=="], - "@gitbook/api": ["@gitbook/api@0.192.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-S9ceF3XspgSqXb7JH5feSHSytVkq1jRDwOKeEK4nnZs9pSiKFxHZMj/LnHi8k8anaue/FeLwWYo+Vk08Pdl0+g=="], + "@gitbook/api": ["@gitbook/api@0.195.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-66OGIiiKUfHB7dF8fb9Ai8PkhQldD4SSQXHZ+X+SSEki2WXpgn6ze+PEXRQvDsJj2JKEvD9A7FfaGzUYiYn9hA=="], "@gitbook/browser-types": ["@gitbook/browser-types@workspace:packages/browser-types"], diff --git a/package.json b/package.json index c69a18743..46fd82c99 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "catalog": { "@tsconfig/strictest": "^2.0.6", "@tsconfig/node20": "^20.1.6", - "@gitbook/api": "0.192.0", + "@gitbook/api": "0.195.0", "@scalar/api-client-react": "^1.3.46", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", diff --git a/packages/gitbook/src/components/AI/useAI.tsx b/packages/gitbook/src/components/AI/useAI.tsx index e8322c1a2..5573feec8 100644 --- a/packages/gitbook/src/components/AI/useAI.tsx +++ b/packages/gitbook/src/components/AI/useAI.tsx @@ -20,7 +20,7 @@ export type AIConfig = { trademark: boolean; assistantName?: string; greeting?: { - title: string; + title?: string; subtitle: string; }; }; diff --git a/packages/gitbook/src/components/AIChat/AIChat.tsx b/packages/gitbook/src/components/AIChat/AIChat.tsx index 3b50ba9d1..03cff48a2 100644 --- a/packages/gitbook/src/components/AIChat/AIChat.tsx +++ b/packages/gitbook/src/components/AIChat/AIChat.tsx @@ -121,6 +121,7 @@ export function AIChat() { suggestions={config.suggestions} trademark={config.trademark} assistantName={config.assistantName} + greeting={config.greeting} /> @@ -212,7 +213,7 @@ export function AIChatBody(props: { /** Custom assistant name override; falls back to the branded/unbranded default name. */ assistantName?: string; greeting?: { - title: string; + title?: string; subtitle: string; }; }) { diff --git a/packages/gitbook/src/components/SiteLayout/SiteLayout.tsx b/packages/gitbook/src/components/SiteLayout/SiteLayout.tsx index a63ac8e6d..f984cd97f 100644 --- a/packages/gitbook/src/components/SiteLayout/SiteLayout.tsx +++ b/packages/gitbook/src/components/SiteLayout/SiteLayout.tsx @@ -30,6 +30,8 @@ export async function SiteLayout(props: { const { context, forcedTheme, withTracking, visitorAuthClaims, children } = props; const { customization } = context; + const { ai } = customization; + const aiGreeting = (context.locale && ai?.localizedGreeting?.[context.locale]) ?? ai?.greeting; // Scripts are disabled when tracking is disabled const scripts = withTracking ? context.scripts : []; @@ -73,6 +75,7 @@ export async function SiteLayout(props: { aiMode={customization.ai?.mode} suggestions={context.customization.ai?.suggestions} trademark={customization.trademark.enabled} + greeting={aiGreeting ? { subtitle: aiGreeting } : undefined} > Date: Thu, 13 Aug 2026 14:33:10 +0200 Subject: [PATCH 03/15] Detect and pass visitor type for site resolution (#4490) --- packages/gitbook/src/lib/visitors.test.ts | 45 +++++++++++++++++++++++ packages/gitbook/src/lib/visitors.ts | 12 ++++++ packages/gitbook/src/middleware.ts | 2 + 3 files changed, 59 insertions(+) diff --git a/packages/gitbook/src/lib/visitors.test.ts b/packages/gitbook/src/lib/visitors.test.ts index 9054b8658..84c5fddd1 100644 --- a/packages/gitbook/src/lib/visitors.test.ts +++ b/packages/gitbook/src/lib/visitors.test.ts @@ -6,6 +6,7 @@ import { getVisitorAuthCookieName, getVisitorAuthCookieValue, getVisitorToken, + getVisitorType, getVisitorUnsignedClaims, normalizeVisitorURL, } from './visitors'; @@ -445,3 +446,47 @@ describe('normalizeVisitorURL', () => { ); }); }); + +describe('getVisitorType', () => { + const requestWith = (headers: Record) => ({ headers: new Headers(headers) }); + + it('should classify a known AI agent user-agent as "agent"', () => { + expect( + getVisitorType( + requestWith({ + 'user-agent': + 'Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)', + }) + ) + ).toBe('agent'); + expect(getVisitorType(requestWith({ 'user-agent': 'ClaudeBot/1.0' }))).toBe('agent'); + }); + + it('should classify a request with a Signature-Agent header as "agent"', () => { + expect( + getVisitorType( + requestWith({ + 'user-agent': 'SomeClient/1.0', + 'signature-agent': '"https://chatgpt.com"', + }) + ) + ).toBe('agent'); + }); + + it('should classify a normal browser user-agent as "human"', () => { + expect( + getVisitorType( + requestWith({ + 'user-agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36', + 'sec-fetch-mode': 'navigate', + }) + ) + ).toBe('human'); + }); + + it('should default to "human" when the user-agent is missing or empty', () => { + expect(getVisitorType(requestWith({}))).toBe('human'); + expect(getVisitorType(requestWith({ 'user-agent': '' }))).toBe('human'); + }); +}); diff --git a/packages/gitbook/src/lib/visitors.ts b/packages/gitbook/src/lib/visitors.ts index 170edf731..0192f9db9 100644 --- a/packages/gitbook/src/lib/visitors.ts +++ b/packages/gitbook/src/lib/visitors.ts @@ -1,3 +1,5 @@ +import type { SiteVisitorPayload } from '@gitbook/api'; +import { isAIAgent } from '@vercel/agent-readability'; import { type JwtPayload, jwtDecode } from 'jwt-decode'; import { type NextRequest, NextResponse } from 'next/server'; import hash from 'object-hash'; @@ -87,6 +89,16 @@ export type VisitorTokenLookup = /** Not visitor token was found */ | undefined; +/** + * Get the type of visitor, i.e human or agent, accessing the published site. + */ +export function getVisitorType(request: { headers: Headers }): NonNullable< + SiteVisitorPayload['type'] +> { + const detection = isAIAgent(request); + return detection.detected && detection.method !== 'heuristic' ? 'agent' : 'human'; +} + /** * Get the visitor data for the request potentially including: * - a JWT token that may contain signed claims or can be used for VA authentication. diff --git a/packages/gitbook/src/middleware.ts b/packages/gitbook/src/middleware.ts index 94aa5deee..4b60d6a59 100644 --- a/packages/gitbook/src/middleware.ts +++ b/packages/gitbook/src/middleware.ts @@ -47,6 +47,7 @@ import { getPathScopedCookieName, getResponseCookiesForVisitorAuth, getVisitorData, + getVisitorType, normalizeVisitorURL, serveVisitorClaimsDataRequest, } from '@/lib/visitors'; @@ -219,6 +220,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) { visitorPayload: { jwtToken: visitorToken?.token ?? undefined, unsignedClaims, + type: getVisitorType(request), }, // When the visitor auth token is pulled from the cookie, set redirectOnError when calling resolvePublishedContentByUrl to allow // redirecting when the token is invalid as we could be dealing with stale token stored in the cookie. From 95d23775cea5b86580d550b9f4ad8c056bdcec6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Berg=C3=A9?= Date: Thu, 13 Aug 2026 15:18:42 +0200 Subject: [PATCH 04/15] Migrate repository to Oxlint and Oxfmt (#4497) --- .editorconfig | 10 + .github/CONTRIBUTING.md | 5 +- .../gradual-deploy-cloudflare/action.yaml | 146 +++++++------- .../composite/deploy-cloudflare/action.yaml | 105 +++++------ .github/composite/deploy-vercel/action.yaml | 119 ++++++------ .github/workflows/ci.yaml | 14 ++ .github/workflows/deploy-preview.yaml | 18 +- .github/workflows/deploy-production.yaml | 18 +- .github/workflows/deploy-staging.yaml | 18 +- .github/workflows/publish.yaml | 2 +- .oxfmtrc.json | 70 +++++++ .oxlintrc.json | 87 +++++++++ .vscode/extensions.json | 2 +- .vscode/settings.json | 8 +- AGENTS.md | 5 +- biome.json | 178 ------------------ bun.lock | 103 ++++++++-- package.json | 13 +- packages/browser-types/package.json | 6 +- packages/cache-tags/package.json | 6 +- packages/cache-tags/src/index.ts | 9 +- packages/colors/package.json | 6 +- packages/embed/package.json | 6 +- .../embed/src/client/createGitBookFrame.ts | 3 +- packages/embed/src/react/GitBookFrame.tsx | 1 + packages/embed/src/react/GitBookProvider.tsx | 1 + packages/embed/src/react/context.ts | 1 + packages/emoji-codepoints/build.ts | 2 +- packages/expr/package.json | 6 +- .../expr/src/__tests__/autocomplete.test.ts | 4 +- packages/expr/src/autocomplete.ts | 9 +- packages/expr/src/input-values.ts | 1 + packages/expr/src/types.ts | 6 +- packages/fonts/bin/generate.ts | 3 +- packages/fonts/package.json | 7 +- packages/fonts/src/fonts.ts | 3 +- packages/fonts/src/getDefaultFont.test.ts | 2 + packages/fonts/src/getDefaultFont.ts | 3 +- packages/gitbook/e2e/internal.spec.ts | 24 +-- packages/gitbook/e2e/pdf.spec.ts | 1 + packages/gitbook/e2e/util.ts | 29 +-- .../gitbook/openNext/customWorkers/default.js | 9 +- .../customWorkers/defaultWrangler.jsonc | 108 +++++------ .../openNext/customWorkers/doWrangler.jsonc | 94 ++++----- .../openNext/customWorkers/middleware.js | 6 +- .../customWorkers/middlewareWrangler.jsonc | 126 ++++++------- .../incrementalCache/incrementalCache.ts | 5 +- .../openNext/incrementalCache/middleware.ts | 1 + .../openNext/incrementalCache/server.ts | 1 + packages/gitbook/openNext/queue/server.ts | 3 +- .../gitbook/openNext/tagCache/middleware.ts | 3 +- .../check-css-browser-compatibility.ts | 1 + .../scripts/generate-mermaid-runtime.ts | 2 +- .../scripts/generate-scalar-runtime.ts | 2 +- .../scripts/profile-opennext-memory.ts | 4 +- packages/gitbook/src/app/global-error.tsx | 6 +- .../[siteData]/(content)/[pagePath]/page.tsx | 3 +- .../[siteURL]/[siteData]/(content)/layout.tsx | 3 +- .../[siteData]/~gitbook/auth/login/route.ts | 3 +- .../[siteData]/~gitbook/auth/logout/route.ts | 7 +- .../~gitbook/embed/assistant/page.tsx | 3 +- .../[siteData]/~gitbook/embed/layout.tsx | 3 +- .../[siteData]/~gitbook/embed/page.tsx | 3 +- .../~gitbook/embed/page/[pagePath]/page.tsx | 3 +- .../[siteData]/~gitbook/mcp/auth/route.ts | 3 +- .../[siteData]/~gitbook/mcp/handler.ts | 7 +- .../[siteData]/~gitbook/mcp/route.ts | 3 +- .../[siteData]/~gitbook/pdf/page.tsx | 6 +- .../[siteData]/~gitbook/search/route.ts | 20 +- .../[siteData]/(content)/[pagePath]/page.tsx | 4 +- .../~gitbook/embed/assistant/page.tsx | 3 +- .../[siteData]/~gitbook/embed/demo/route.ts | 3 +- .../[siteData]/~gitbook/embed/page.tsx | 3 +- .../~gitbook/embed/page/[pagePath]/page.tsx | 3 +- .../~gitbook/embed/script.js/route.ts | 6 +- .../markdown-ask/[question]/[goal]/route.ts | 3 +- .../~gitbook/markdown-ask/[question]/route.ts | 3 +- .../~gitbook/markdown/[pagePath]/route.ts | 3 +- .../~gitbook/rss/[pagePath]/route.ts | 3 +- .../[siteData]/~gitbook/site-index/route.ts | 3 +- packages/gitbook/src/app/utils.ts | 10 +- .../src/app/~gitbook/revalidate/route.ts | 2 +- .../~space/[spaceId]/~gitbook/pdf/page.tsx | 6 +- .../components/AI/controls/ConfirmControl.tsx | 5 +- .../AI/controls/MultiChoiceControl.tsx | 18 +- .../AI/controls/SingleChoiceControl.tsx | 15 +- .../src/components/AI/controls/common.tsx | 3 +- .../src/components/AI/controls/helpers.ts | 10 +- .../src/components/AI/navigation.test.ts | 1 + .../gitbook/src/components/AI/navigation.ts | 2 +- .../src/components/AI/references.test.ts | 1 + .../AI/server-actions/AIMessageView.tsx | 7 +- .../AI/server-actions/AIToolCallsSummary.tsx | 36 ++-- .../src/components/AI/server-actions/api.tsx | 10 +- .../src/components/AI/server-actions/chat.ts | 9 +- .../components/AI/server-actions/navigate.ts | 2 +- packages/gitbook/src/components/AI/tools.ts | 1 + packages/gitbook/src/components/AI/useAI.tsx | 9 +- .../gitbook/src/components/AI/useAIChat.tsx | 11 +- .../src/components/AI/useAIMessageContext.ts | 4 +- .../components/AI/useNavigateToPageTool.ts | 12 +- .../AI/useSubmitAssistantFeedbackTool.ts | 12 +- .../AI/useSubmitPageFeedbackTool.ts | 14 +- .../gitbook/src/components/AIChat/AIChat.tsx | 35 ++-- .../src/components/AIChat/AIChatButton.tsx | 4 +- .../components/AIChat/AIChatControlButton.tsx | 4 +- .../components/AIChat/AIChatExpandButton.tsx | 3 +- .../src/components/AIChat/AIChatIcon.tsx | 3 +- .../src/components/AIChat/AIChatInput.tsx | 12 +- .../src/components/AIChat/AIChatMessages.tsx | 30 +-- .../components/AIChat/AIChatQueuedMessage.tsx | 7 +- .../AIChat/AIChatReferenceChips.tsx | 9 +- .../components/AIChat/AIChatResizeHandle.tsx | 5 +- .../AIChat/AIChatSuggestedQuestions.tsx | 8 +- .../components/AIChat/AIResponseFeedback.tsx | 9 +- .../AIChat/AiChatFollowupSuggestions.tsx | 2 +- .../AskAIParagraphButton.tsx | 3 +- .../AskAITextSelection/AskAITextSelection.tsx | 5 +- .../components/AIChat/useAIChatWidthStore.ts | 5 +- .../AdaptiveVisitorContextProvider.tsx | 6 +- .../gitbook/src/components/Adaptive/utils.ts | 1 + .../components/AdminToolbar/AdminToolbar.tsx | 2 +- .../AdminToolbar/AdminToolbarClient.tsx | 14 +- .../components/AdminToolbar/AnimatedLogo.tsx | 3 +- .../AdminToolbar/ChangedPagesButton.tsx | 16 +- .../AdminToolbar/HideToolbarButton.tsx | 10 +- .../src/components/AdminToolbar/Toolbar.tsx | 17 +- .../components/AdminToolbar/ToolbarDate.tsx | 4 +- .../AdminToolbar/ToolbarVisibilityHint.tsx | 5 +- .../components/AdminToolbar/changedPages.ts | 4 +- .../AdminToolbar/changedPagesMapper.ts | 9 +- .../AdminToolbar/useMagnificationEffect.ts | 8 +- packages/gitbook/src/components/Ads/Ad.tsx | 10 +- .../src/components/Ads/AdClassicRendering.tsx | 7 +- .../src/components/Ads/AdCoverRendering.tsx | 7 +- .../gitbook/src/components/Ads/renderAd.tsx | 7 +- packages/gitbook/src/components/Ads/types.ts | 2 +- .../components/Announcement/Announcement.tsx | 6 +- .../Announcement/AnnouncementBanner.tsx | 21 ++- .../AnnouncementDismissedScript.tsx | 1 + .../AutoRefreshContent/server-actions.ts | 4 +- .../src/components/Cookies/CookiesToast.tsx | 13 +- .../DocumentView/Annotation/Annotation.tsx | 3 +- .../Annotation/AnnotationPopover.tsx | 4 +- .../src/components/DocumentView/Block.tsx | 19 +- .../DocumentView/BlockContentRef.tsx | 5 +- .../src/components/DocumentView/Blocks.tsx | 17 +- .../src/components/DocumentView/Caption.tsx | 5 +- .../CodeBlock/AskAICodeButton.tsx | 3 +- .../CodeBlock/ClientCodeBlock.tsx | 17 +- .../CodeBlock/ClientPlainCodeBlock.tsx | 3 +- .../DocumentView/CodeBlock/CodeBlock.tsx | 7 +- .../CodeBlock/CodeBlockRenderer.tsx | 6 +- .../DocumentView/CodeBlock/CopyCodeButton.tsx | 3 +- .../CodeBlock/MermaidCodeBlock.tsx | 18 +- .../CodeBlock/MermaidCodeBlockLazy.tsx | 1 + .../DocumentView/CodeBlock/PlainCodeBlock.tsx | 3 +- .../CodeBlock/highlight-tokens.ts | 3 +- .../DocumentView/CodeBlock/highlight.test.ts | 1 + .../DocumentView/CodeBlock/highlight.ts | 3 +- .../DocumentView/CodeBlock/plain-highlight.ts | 5 +- .../DocumentView/CodeBlock/utils.test.ts | 1 + .../DocumentView/Columns/Columns.tsx | 6 +- .../src/components/DocumentView/Divider.tsx | 3 +- .../components/DocumentView/DocumentView.tsx | 4 +- .../src/components/DocumentView/Drawing.tsx | 3 +- .../src/components/DocumentView/Embed.tsx | 8 +- .../src/components/DocumentView/Emoji.tsx | 3 +- .../DocumentView/Expandable/Expandable.tsx | 11 +- .../src/components/DocumentView/File.tsx | 10 +- .../DocumentView/HashLinkButton.tsx | 3 +- .../src/components/DocumentView/Heading.tsx | 7 +- .../src/components/DocumentView/Hint.tsx | 9 +- .../src/components/DocumentView/Images.tsx | 9 +- .../src/components/DocumentView/Inline.tsx | 2 +- .../DocumentView/InlineActionButton.tsx | 4 +- .../components/DocumentView/InlineButton.tsx | 12 +- .../InlineExpression/InlineExpression.tsx | 1 + .../InlineExpressionValue.tsx | 3 +- .../useEvaluateInlineExpression.ts | 5 +- .../components/DocumentView/InlineIcon.tsx | 4 +- .../components/DocumentView/InlineImage.tsx | 8 +- .../DocumentView/InlineLink/InlineLink.tsx | 12 +- .../InlineLink/InlineLinkTooltip.tsx | 10 +- .../ContentKitWithClientContext.tsx | 10 +- .../Integration/IntegrationBlock.tsx | 10 +- .../DocumentView/Integration/adaptive.test.ts | 3 +- .../DocumentView/Integration/adaptive.ts | 3 +- .../DocumentView/Integration/contentkit.tsx | 3 +- .../DocumentView/Integration/render.ts | 3 +- .../Integration/server-actions.tsx | 5 +- .../src/components/DocumentView/List.tsx | 3 +- .../src/components/DocumentView/ListItem.tsx | 12 +- .../src/components/DocumentView/Math.tsx | 5 +- .../src/components/DocumentView/Mention.tsx | 3 +- .../DocumentView/NotFoundRefHoverCard.tsx | 5 +- .../OpenAPI/OpenAPIBlockClient.tsx | 3 +- .../DocumentView/OpenAPI/OpenAPIBlockLazy.tsx | 1 + .../DocumentView/OpenAPI/OpenAPIOperation.tsx | 9 +- .../DocumentView/OpenAPI/OpenAPISchemas.tsx | 9 +- .../DocumentView/OpenAPI/OpenAPIWebhook.tsx | 9 +- .../DocumentView/OpenAPI/context.tsx | 3 +- .../src/components/DocumentView/Paragraph.tsx | 7 +- .../components/DocumentView/Prompt/Prompt.tsx | 1 + .../DocumentView/Prompt/PromptClient.tsx | 10 +- .../DocumentView/ReusableContent.tsx | 4 +- .../DocumentView/SelectActionButton.tsx | 2 +- .../components/DocumentView/StepperStep.tsx | 6 +- .../DocumentView/Table/RecordCard.tsx | 11 +- .../DocumentView/Table/RecordColumnValue.tsx | 19 +- .../DocumentView/Table/RecordRow.tsx | 5 +- .../DocumentView/Table/StickyViewGrid.tsx | 3 +- .../components/DocumentView/Table/Table.tsx | 12 +- .../DocumentView/Table/TableSearch.tsx | 10 +- .../DocumentView/Table/ViewCards.tsx | 5 +- .../DocumentView/Table/ViewGrid.tsx | 5 +- .../components/DocumentView/Table/utils.ts | 3 +- .../DocumentView/Tabs/DynamicTabs.tsx | 5 +- .../src/components/DocumentView/Tabs/Tabs.tsx | 7 +- .../src/components/DocumentView/Text.tsx | 3 +- .../src/components/DocumentView/Update.tsx | 20 +- .../src/components/DocumentView/Updates.tsx | 1 + .../components/DocumentView/UpdatesFilter.tsx | 19 +- .../DocumentView/selectAction.test.ts | 2 + .../utils/isBlockOffscreen.test.ts | 1 + .../DocumentView/utils/isBlockOffscreen.ts | 3 +- .../DocumentView/utils/textAlignment.ts | 3 +- .../Embeddable/EmbeddableAIChat.tsx | 22 ++- .../EmbeddableAIContextProvider.tsx | 3 +- .../Embeddable/EmbeddableDocsPage.tsx | 18 +- .../EmbeddableDocsPageControlButtons.tsx | 4 +- .../components/Embeddable/EmbeddableFrame.tsx | 30 +-- .../Embeddable/EmbeddableIframeAPI.tsx | 23 ++- .../Embeddable/EmbeddableRootLayout.tsx | 19 +- .../Embeddable/EmbeddableSearch.tsx | 17 +- .../Embeddable/EmbeddableSearchPage.tsx | 2 +- .../Embeddable/EmbeddableTrademark.tsx | 5 +- .../src/components/Embeddable/channel.ts | 4 +- .../gitbook/src/components/Footer/Footer.tsx | 17 +- .../components/Footer/FooterLinksGroup.tsx | 9 +- .../src/components/Footer/SocialAccounts.tsx | 5 +- .../components/Header/CurrentContentIcon.tsx | 5 +- .../gitbook/src/components/Header/Header.tsx | 20 +- .../src/components/Header/HeaderLink.tsx | 10 +- .../components/Header/HeaderLinkDropdown.tsx | 9 +- .../src/components/Header/HeaderLinkMore.tsx | 16 +- .../Header/HeaderLinkMoreClient.tsx | 5 +- .../src/components/Header/HeaderLinks.tsx | 3 +- .../src/components/Header/HeaderLogo.tsx | 10 +- .../components/Header/HeaderMobileMenu.tsx | 3 +- .../src/components/Header/SpacesDropdown.tsx | 4 +- .../Header/SpacesDropdownClient.tsx | 6 +- .../Header/SpacesDropdownMenuItem.tsx | 7 +- .../components/Insights/InsightsProvider.tsx | 12 +- .../Insights/TrackPageViewEvent.tsx | 7 +- .../src/components/Insights/sessions.ts | 3 +- .../src/components/Insights/visitorId.tsx | 18 +- .../Integrations/LoadIntegrations.tsx | 22 ++- .../components/PDF/ImagesLoadingStatus.tsx | 11 +- .../src/components/PDF/PDFPage.test.ts | 4 +- .../gitbook/src/components/PDF/PDFPage.tsx | 41 ++-- .../src/components/PDF/PDFPrintControls.tsx | 6 +- .../src/components/PDF/PDFRootLayout.tsx | 3 +- .../src/components/PDF/PageControlButtons.tsx | 7 +- packages/gitbook/src/components/PDF/linker.ts | 2 +- .../components/PageActions/PageActions.tsx | 18 +- .../PageActions/PageActionsDropdown.tsx | 10 +- .../src/components/PageAside/PageAside.tsx | 24 ++- .../components/PageAside/PageAsideButton.tsx | 7 +- .../PageAside/ScrollSectionsList.tsx | 10 +- .../PageBody/BreadcrumbItemDropdown.tsx | 3 +- .../src/components/PageBody/PageBody.tsx | 28 +-- .../PageBody/PageBodyBlankslate.tsx | 7 +- .../src/components/PageBody/PageCover.tsx | 16 +- .../components/PageBody/PageCoverImage.tsx | 2 +- .../PageBody/PageFooterNavigation.tsx | 12 +- .../src/components/PageBody/PageHeader.tsx | 30 +-- .../src/components/PageBody/PageTags.tsx | 3 +- .../PageFeedback/PageFeedbackForm.tsx | 16 +- .../RootLayout/CustomizationRootLayout.tsx | 17 +- .../RootLayout/RootLayoutClientContexts.tsx | 6 +- .../components/Search/LocalSearchResults.tsx | 5 +- .../src/components/Search/SearchAskAnswer.tsx | 15 +- .../src/components/Search/SearchAskBar.tsx | 10 +- .../src/components/Search/SearchContainer.tsx | 12 +- .../src/components/Search/SearchFrame.tsx | 9 +- .../src/components/Search/SearchInput.tsx | 11 +- .../Search/SearchPageResultItem.tsx | 21 +-- .../Search/SearchQuestionResultItem.tsx | 6 +- .../Search/SearchRecordResultItem.tsx | 10 +- .../components/Search/SearchResultItem.tsx | 6 +- .../src/components/Search/SearchResults.tsx | 11 +- .../components/Search/SearchScopeControl.tsx | 11 +- .../src/components/Search/filter.test.ts | 1 + .../Search/getPageResultHref.test.ts | 1 + .../Search/reciprocalRankFusion.test.ts | 1 + .../components/Search/reciprocalRankFusion.ts | 2 +- .../src/components/Search/search-props.ts | 5 +- .../src/components/Search/search-types.ts | 2 +- .../src/components/Search/server-actions.tsx | 21 ++- .../Search/useLocalSearchResults.tsx | 1 + .../src/components/Search/useSearch.test.ts | 1 + .../src/components/Search/useSearch.tsx | 1 + .../components/Search/useSearchController.tsx | 5 +- .../src/components/Search/useSearchResults.ts | 12 +- .../src/components/Select/SelectProvider.tsx | 3 +- .../components/Select/SelectStateScript.tsx | 2 +- .../src/components/Select/useSelect.ts | 3 +- .../src/components/Select/useSelectAnchor.ts | 3 +- .../components/SiteAuth/SiteAuthLoginLink.tsx | 2 +- .../src/components/SiteLayout/SiteLayout.tsx | 11 +- .../SiteLayout/SiteLayoutClientContexts.tsx | 8 +- .../SiteOAuthConsent/ConsentForm.tsx | 5 +- .../SiteOAuthConsent/ConsentScreen.tsx | 15 +- .../components/SitePage/PageClientLayout.tsx | 12 +- .../src/components/SitePage/SitePage.tsx | 30 +-- .../components/SitePage/SitePageNotFound.tsx | 22 ++- .../components/SitePage/SitePageSkeleton.tsx | 2 +- .../gitbook/src/components/SitePage/fetch.ts | 2 +- .../SiteSections/SiteSectionList.tsx | 16 +- .../SiteSections/SiteSectionTabs.tsx | 17 +- .../SiteSections/encodeClientSiteSections.ts | 6 +- .../SpaceLayout/SpaceLayout.test.ts | 4 +- .../components/SpaceLayout/SpaceLayout.tsx | 35 ++-- .../TableOfContents/BackToSpaceButton.tsx | 7 +- .../TableOfContents/PageDocumentItem.tsx | 5 +- .../TableOfContents/PageGroupItem.tsx | 7 +- .../TableOfContents/PageLinkItem.tsx | 7 +- .../components/TableOfContents/PagesList.tsx | 13 +- .../TableOfContents/TOCPageIcon.tsx | 3 +- .../TableOfContents/TableOfContents.tsx | 13 +- .../TableOfContents/ToggleableLinkItem.tsx | 12 +- .../components/TableOfContents/Trademark.tsx | 7 +- .../encodeClientTableOfContents.ts | 6 +- packages/gitbook/src/components/Tag.tsx | 5 +- .../components/ThemeToggler/ThemeToggler.tsx | 5 +- .../components/hooks/useCurrentContent.tsx | 3 +- .../hooks/useCurrentPageMetadata.tsx | 4 +- .../components/hooks/useCurrentPagePath.ts | 2 +- .../src/components/hooks/useScrollOverflow.ts | 1 + .../src/components/hooks/useScrollPage.ts | 2 +- .../src/components/primitives/Button.tsx | 4 +- .../src/components/primitives/Card.tsx | 3 +- .../src/components/primitives/Checkbox.tsx | 3 +- .../src/components/primitives/Collapsible.tsx | 3 +- .../components/primitives/DateRelative.tsx | 2 +- .../components/primitives/DropdownMenu.tsx | 18 +- .../src/components/primitives/HoverCard.tsx | 5 +- .../src/components/primitives/Input.tsx | 12 +- .../primitives/KeyboardShortcut.tsx | 6 +- .../src/components/primitives/Link.tsx | 6 +- .../primitives/LoadingStateProvider.tsx | 6 +- .../primitives/NavigationLoader.tsx | 3 +- .../src/components/primitives/Popover.tsx | 5 +- .../components/primitives/ScrollContainer.tsx | 5 +- .../primitives/SegmentedControl.tsx | 2 +- .../src/components/primitives/SideSheet.tsx | 11 +- .../src/components/primitives/Skeleton.tsx | 3 +- .../components/primitives/StyleProvider.tsx | 3 +- .../src/components/primitives/StyledLink.tsx | 3 +- .../components/primitives/ToggleChevron.tsx | 3 +- .../src/components/primitives/Tooltip.tsx | 3 +- .../gitbook/src/components/utils/Image.tsx | 8 +- .../src/components/utils/ZoomImage.tsx | 4 +- .../gitbook/src/components/utils/dates.ts | 2 +- packages/gitbook/src/fonts/custom.test.ts | 4 +- packages/gitbook/src/fonts/default.ts | 3 +- packages/gitbook/src/fonts/index.ts | 1 + packages/gitbook/src/intl/server.ts | 2 +- packages/gitbook/src/lib/adaptive.ts | 6 +- packages/gitbook/src/lib/ask.ts | 3 +- packages/gitbook/src/lib/assets.ts | 2 +- .../gitbook/src/lib/auth-login-link.test.ts | 1 + packages/gitbook/src/lib/browser/cookies.ts | 4 +- packages/gitbook/src/lib/cache.test.ts | 1 + packages/gitbook/src/lib/colors.ts | 3 +- packages/gitbook/src/lib/context.ts | 34 ++-- .../src/lib/cssBrowserCompatibility.test.ts | 7 +- packages/gitbook/src/lib/customization.ts | 6 +- packages/gitbook/src/lib/data/api.ts | 10 +- packages/gitbook/src/lib/data/cloudflare.ts | 1 + packages/gitbook/src/lib/data/errors.test.ts | 2 + packages/gitbook/src/lib/data/errors.ts | 4 +- packages/gitbook/src/lib/data/lookup.ts | 7 +- packages/gitbook/src/lib/data/pages.ts | 1 + packages/gitbook/src/lib/data/revisions.ts | 3 +- packages/gitbook/src/lib/data/types.ts | 27 +-- packages/gitbook/src/lib/data/urls.ts | 2 +- packages/gitbook/src/lib/data/visitor.test.ts | 1 + packages/gitbook/src/lib/data/visitor.ts | 3 +- .../gitbook/src/lib/document-sections.test.ts | 6 +- packages/gitbook/src/lib/document-sections.ts | 5 +- packages/gitbook/src/lib/document.tsx | 8 +- packages/gitbook/src/lib/embeddable.test.ts | 2 + packages/gitbook/src/lib/embeddable.ts | 5 +- packages/gitbook/src/lib/icons/inline.test.ts | 1 + packages/gitbook/src/lib/icons/inline.ts | 4 +- packages/gitbook/src/lib/imageFonts.ts | 3 +- .../lib/images/checkIsSizableImageURL.test.ts | 1 + .../src/lib/images/checkIsSizableImageURL.ts | 4 +- .../images/getImageResizingContextId.test.ts | 1 + .../src/lib/images/resizer/cf-fetch.ts | 2 +- .../src/lib/images/resizer/gitbook-service.ts | 4 +- .../src/lib/images/resizer/resizeImage.ts | 3 +- packages/gitbook/src/lib/images/signatures.ts | 4 +- .../gitbook/src/lib/indexing-crawlers.test.ts | 1 + packages/gitbook/src/lib/links.test.ts | 1 + packages/gitbook/src/lib/links.ts | 15 +- .../gitbook/src/lib/llms-directive.test.ts | 2 + packages/gitbook/src/lib/llms-directive.ts | 1 + packages/gitbook/src/lib/logger.ts | 2 +- packages/gitbook/src/lib/markdownPage.ts | 42 +++-- packages/gitbook/src/lib/middleware.ts | 4 +- packages/gitbook/src/lib/oauth-protected.ts | 4 +- .../gitbook/src/lib/openapi/enrich.test.ts | 1 + packages/gitbook/src/lib/openapi/fetch.ts | 11 +- .../src/lib/openapi/proxy-token.test.ts | 29 ++- .../gitbook/src/lib/openapi/proxy-token.ts | 1 + .../openapi/resolveOpenAPIOperationBlock.ts | 3 +- .../lib/openapi/resolveOpenAPISchemasBlock.ts | 1 + .../lib/openapi/resolveOpenAPIWebhookBlock.ts | 3 +- packages/gitbook/src/lib/openapi/types.ts | 3 +- packages/gitbook/src/lib/pages.test.ts | 1 + packages/gitbook/src/lib/pages.ts | 4 +- packages/gitbook/src/lib/paths.test.ts | 1 + packages/gitbook/src/lib/preview.test.ts | 1 + packages/gitbook/src/lib/preview.ts | 4 +- packages/gitbook/src/lib/proxy.test.ts | 1 + packages/gitbook/src/lib/references.test.ts | 3 +- packages/gitbook/src/lib/references.tsx | 46 ++--- packages/gitbook/src/lib/routes.ts | 3 +- .../src/lib/select/generateSelectCSS.test.ts | 1 + packages/gitbook/src/lib/select/slug.test.ts | 3 +- .../src/lib/select/store.hydration.test.ts | 1 + packages/gitbook/src/lib/select/store.test.ts | 1 + packages/gitbook/src/lib/select/store.ts | 2 +- packages/gitbook/src/lib/seo.ts | 5 +- packages/gitbook/src/lib/site-oauth/index.ts | 1 - packages/gitbook/src/lib/sitemap.test.ts | 1 + packages/gitbook/src/lib/sitemap.ts | 4 +- packages/gitbook/src/lib/sites.test.ts | 3 +- packages/gitbook/src/lib/sites.ts | 5 +- packages/gitbook/src/lib/tracking.ts | 4 +- packages/gitbook/src/lib/typescript.ts | 2 +- packages/gitbook/src/lib/updates.test.ts | 2 + packages/gitbook/src/lib/updates.ts | 1 + packages/gitbook/src/lib/visitors.test.ts | 2 +- packages/gitbook/src/lib/visitors.ts | 9 +- packages/gitbook/src/lib/waitUntil.ts | 2 +- packages/gitbook/src/middleware.ts | 27 +-- .../pages/api/~gitbook/force-revalidate.ts | 4 +- packages/gitbook/src/routes/icon.tsx | 5 +- packages/gitbook/src/routes/image.ts | 3 +- packages/gitbook/src/routes/llms-full.ts | 10 +- packages/gitbook/src/routes/llms.ts | 10 +- packages/gitbook/src/routes/markdownPage.ts | 3 +- packages/gitbook/src/routes/ogimage.tsx | 15 +- .../gitbook/src/routes/openapi-proxy.test.ts | 9 +- packages/gitbook/src/routes/openapi-proxy.ts | 3 +- packages/gitbook/src/routes/robots.ts | 3 +- packages/gitbook/src/routes/rss.ts | 8 +- packages/gitbook/src/routes/sitemap.ts | 3 +- packages/gitbook/tailwind.config.ts | 2 +- packages/gitbook/tests/cors.test.ts | 1 + packages/gitbook/tests/llms.test.ts | 1 + packages/gitbook/tests/markdown.test.ts | 1 + packages/gitbook/tests/mcp.test.ts | 3 +- packages/gitbook/tests/robots.test.ts | 1 + packages/gitbook/tests/rss.test.ts | 1 + packages/gitbook/tsconfig.json | 86 ++++----- packages/gitbook/types/memoizee.d.ts | 4 +- packages/gitbook/types/navigator.d.ts | 2 - packages/gitbook/wrangler.jsonc | 96 +++++----- packages/icons/bin/gen-list.js | 2 +- packages/icons/bin/gitbook-icons.js | 2 +- packages/icons/package.json | 7 +- packages/icons/src/Icon.tsx | 2 +- packages/icons/src/IconsProvider.tsx | 1 + packages/icons/src/pro.test.ts | 1 + packages/openapi-parser/package.json | 6 +- .../openapi-parser/src/filesystem.test.ts | 3 +- packages/openapi-parser/src/filesystem.ts | 1 + packages/openapi-parser/src/parse.test.ts | 3 +- packages/openapi-parser/src/parse.ts | 1 + packages/openapi-parser/src/schemas.test.ts | 1 + packages/openapi-parser/src/schemas.ts | 1 + packages/openapi-parser/src/traverse.test.ts | 3 +- packages/openapi-parser/src/v2.test.ts | 1 + packages/openapi-parser/src/v2.ts | 1 + packages/openapi-parser/src/v3.ts | 1 + packages/react-contentkit/package.json | 6 +- packages/react-contentkit/src/ContentKit.tsx | 3 +- packages/react-contentkit/src/Element.tsx | 5 +- .../react-contentkit/src/ElementBlock.tsx | 3 +- packages/react-contentkit/src/ElementBox.tsx | 3 +- .../react-contentkit/src/ElementButton.tsx | 5 +- packages/react-contentkit/src/ElementCard.tsx | 1 + .../react-contentkit/src/ElementCodeBlock.tsx | 2 +- .../src/ElementCodeBlockClient.tsx | 3 +- .../react-contentkit/src/ElementDivider.tsx | 3 +- packages/react-contentkit/src/ElementIcon.tsx | 1 + .../react-contentkit/src/ElementInput.tsx | 4 +- .../react-contentkit/src/ElementMarkdown.tsx | 2 +- .../src/ElementMarkdownClient.tsx | 3 +- .../react-contentkit/src/ElementModal.tsx | 5 +- .../react-contentkit/src/ElementStack.tsx | 5 +- packages/react-contentkit/src/ElementText.tsx | 5 +- .../react-contentkit/src/ElementTextInput.tsx | 5 +- .../react-contentkit/src/ElementWebframe.tsx | 3 +- packages/react-contentkit/src/context.ts | 3 +- packages/react-contentkit/src/types.ts | 3 +- packages/react-math/bin/gitbook-math.js | 10 +- packages/react-math/package.json | 8 +- packages/react-math/src/MathFormula.tsx | 2 +- packages/react-math/src/MathJaXLazy.tsx | 1 + packages/react-openapi/package.json | 6 +- .../react-openapi/src/InteractiveSection.tsx | 5 +- .../src/OpenAPICodeSample.test.ts | 1 + .../react-openapi/src/OpenAPICodeSample.tsx | 38 ++-- .../src/OpenAPICodeSampleSelector.tsx | 5 +- .../react-openapi/src/OpenAPICopyButton.tsx | 3 +- .../src/OpenAPIDisclosureGroup.tsx | 3 +- packages/react-openapi/src/OpenAPIExample.tsx | 12 +- .../react-openapi/src/OpenAPIMediaType.tsx | 2 +- .../react-openapi/src/OpenAPIMethodBadge.tsx | 1 + .../react-openapi/src/OpenAPIOperation.tsx | 5 +- .../src/OpenAPIOperationDescription.tsx | 3 +- .../src/OpenAPIOperationStability.tsx | 1 + packages/react-openapi/src/OpenAPIPath.tsx | 4 +- .../react-openapi/src/OpenAPIPathItem.tsx | 2 +- .../src/OpenAPIPathMultipleServers.tsx | 4 +- .../react-openapi/src/OpenAPIRequestBody.tsx | 3 +- .../src/OpenAPIRequestBodyHeaderType.tsx | 1 + .../src/OpenAPIRequiredScopes.tsx | 12 +- .../react-openapi/src/OpenAPIResponse.tsx | 3 +- .../src/OpenAPIResponseExample.tsx | 3 +- .../src/OpenAPIResponseExampleContent.tsx | 9 +- .../react-openapi/src/OpenAPIResponses.tsx | 6 +- .../react-openapi/src/OpenAPISchema.test.ts | 4 +- packages/react-openapi/src/OpenAPISchema.tsx | 35 ++-- .../react-openapi/src/OpenAPISchemaName.tsx | 4 +- .../react-openapi/src/OpenAPISchemaServer.tsx | 5 +- .../react-openapi/src/OpenAPISecurities.tsx | 6 +- packages/react-openapi/src/OpenAPISelect.tsx | 3 +- packages/react-openapi/src/OpenAPISpec.tsx | 10 +- packages/react-openapi/src/OpenAPIWebhook.tsx | 3 +- .../src/OpenAPIWebhookExample.tsx | 3 +- .../react-openapi/src/ScalarApiButton.tsx | 3 +- packages/react-openapi/src/ScalarApiModal.tsx | 3 +- .../react-openapi/src/code-samples.test.ts | 1 + packages/react-openapi/src/code-samples.ts | 3 +- .../src/common/OpenAPIColumnSpec.tsx | 2 +- .../src/common/OpenAPIMcpBadge.tsx | 2 +- .../common/OpenAPIOperationDescription.tsx | 3 +- .../src/common/OpenAPISummary.tsx | 2 +- packages/react-openapi/src/context.ts | 6 +- .../src/generateSchemaExample.test.ts | 1 + .../src/generateSchemaExample.ts | 3 +- .../react-openapi/src/getDisclosureLabel.ts | 1 + .../src/resolveOpenAPIOperation.test.ts | 1 + .../src/resolveOpenAPIOperation.ts | 1 + .../src/resolveOpenAPIWebhook.ts | 1 + .../src/schemas/OpenAPISchemaItem.tsx | 10 +- .../src/schemas/OpenAPISchemas.tsx | 10 +- .../src/schemas/resolveOpenAPISchemas.test.ts | 1 + .../src/schemas/resolveOpenAPISchemas.ts | 1 + packages/react-openapi/src/util/example.tsx | 3 +- .../react-openapi/src/util/headers.test.ts | 1 + .../react-openapi/src/util/server.test.ts | 2 + packages/react-openapi/src/util/server.ts | 2 +- .../src/util/tryit-prefill.test.ts | 1 + .../react-openapi/src/util/tryit-prefill.ts | 4 +- packages/react-openapi/src/utils.test.ts | 2 + packages/react-openapi/src/utils.ts | 5 +- 574 files changed, 2658 insertions(+), 2292 deletions(-) create mode 100644 .editorconfig create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json delete mode 100644 biome.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..ac3fb0063 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# Stop editors from looking for .editorconfig files in parent directories. +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +max_line_length = 100 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d5c6f5a21..182cb6f2e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -76,7 +76,10 @@ bun dev ``` Additional development commands: -- `bun format`: Format the code using Biome +- `bun lint`: Lint the code using Oxlint +- `bun lint:fix`: Automatically fix lint issues using Oxlint +- `bun format`: Format the code using Oxfmt +- `bun format:check`: Check formatting without changing files - `bun typecheck`: Run TypeScript type checking - `bun unit`: Run unit tests - `bun e2e`: Run end-to-end tests diff --git a/.github/actions/gradual-deploy-cloudflare/action.yaml b/.github/actions/gradual-deploy-cloudflare/action.yaml index 48b078f96..4f4317047 100644 --- a/.github/actions/gradual-deploy-cloudflare/action.yaml +++ b/.github/actions/gradual-deploy-cloudflare/action.yaml @@ -1,83 +1,83 @@ name: Gradual Deploy to Cloudflare description: Use gradual deployment to deploy to Cloudflare. This action will upload the middleware and server versions to Cloudflare and kept them bound together inputs: - apiToken: - description: 'Cloudflare API token' - required: true - accountId: - description: 'Cloudflare account ID' - required: true - environment: - description: 'Cloudflare environment to deploy to (staging, production, preview)' - required: true - middlewareVersionId: - description: 'Middleware version ID to deploy' - required: true - serverVersionId: - description: 'Server version ID to deploy' - required: true + apiToken: + description: 'Cloudflare API token' + required: true + accountId: + description: 'Cloudflare account ID' + required: true + environment: + description: 'Cloudflare environment to deploy to (staging, production, preview)' + required: true + middlewareVersionId: + description: 'Middleware version ID to deploy' + required: true + serverVersionId: + description: 'Server version ID to deploy' + required: true outputs: - deployment-url: - description: "Deployment URL" - value: ${{ steps.deploy_middleware.outputs.deployment-url }} + deployment-url: + description: 'Deployment URL' + value: ${{ steps.deploy_middleware.outputs.deployment-url }} runs: - using: 'composite' - steps: - - id: wrangler_status - name: Check wrangler deployment status - uses: cloudflare/wrangler-action@v3.14.0 - with: - apiToken: ${{ inputs.apiToken }} - accountId: ${{ inputs.accountId }} - workingDirectory: ./ - wranglerVersion: '4.43.0' - environment: ${{ inputs.environment }} - command: deployments status --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc + using: 'composite' + steps: + - id: wrangler_status + name: Check wrangler deployment status + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.43.0' + environment: ${{ inputs.environment }} + command: deployments status --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc - # This step is used to get the version ID that is currently deployed to Cloudflare. - - id: extract_current_version - name: Extract current version - shell: bash - run: | - version_id=$(echo "${{ steps.wrangler_status.outputs.command-output }}" | grep -A 3 "(100%)" | grep -oP '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') - echo "version_id=$version_id" >> $GITHUB_OUTPUT + # This step is used to get the version ID that is currently deployed to Cloudflare. + - id: extract_current_version + name: Extract current version + shell: bash + run: | + version_id=$(echo "${{ steps.wrangler_status.outputs.command-output }}" | grep -A 3 "(100%)" | grep -oP '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + echo "version_id=$version_id" >> $GITHUB_OUTPUT - - id: deploy_server - name: Deploy server to Cloudflare at 0% - uses: cloudflare/wrangler-action@v3.14.0 - with: - apiToken: ${{ inputs.apiToken }} - accountId: ${{ inputs.accountId }} - workingDirectory: ./ - wranglerVersion: '4.43.0' - environment: ${{ inputs.environment }} - command: versions deploy ${{ steps.extract_current_version.outputs.version_id }}@100% ${{ inputs.serverVersionId }}@0% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc + - id: deploy_server + name: Deploy server to Cloudflare at 0% + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.43.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ steps.extract_current_version.outputs.version_id }}@100% ${{ inputs.serverVersionId }}@0% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc - # Since we use version overrides headers, we can directly deploy the middleware to 100%. - - id: deploy_middleware - name: Deploy middleware to Cloudflare at 100% - uses: cloudflare/wrangler-action@v3.14.0 - with: - apiToken: ${{ inputs.apiToken }} - accountId: ${{ inputs.accountId }} - workingDirectory: ./ - wranglerVersion: '4.43.0' - environment: ${{ inputs.environment }} - command: versions deploy ${{ inputs.middlewareVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc + # Since we use version overrides headers, we can directly deploy the middleware to 100%. + - id: deploy_middleware + name: Deploy middleware to Cloudflare at 100% + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.43.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ inputs.middlewareVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc - - name: Deploy server to Cloudflare at 100% - uses: cloudflare/wrangler-action@v3.14.0 - with: - apiToken: ${{ inputs.apiToken }} - accountId: ${{ inputs.accountId }} - workingDirectory: ./ - wranglerVersion: '4.43.0' - environment: ${{ inputs.environment }} - command: versions deploy ${{ inputs.serverVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc + - name: Deploy server to Cloudflare at 100% + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.43.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ inputs.serverVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc - - name: Outputs - shell: bash - env: - DEPLOYMENT_URL: ${{ steps.deploy_middleware.outputs.deployment-url }} - run: | - echo "URL: ${{ steps.deploy_middleware.outputs.deployment-url }}" \ No newline at end of file + - name: Outputs + shell: bash + env: + DEPLOYMENT_URL: ${{ steps.deploy_middleware.outputs.deployment-url }} + run: | + echo "URL: ${{ steps.deploy_middleware.outputs.deployment-url }}" diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index 28055170d..77bc5689d 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -1,34 +1,34 @@ name: 'Deploy cloudflare' description: 'Deploy GitBook to Cloudflare' inputs: - opItem: - description: '1Password item to load secrets from' - required: true - opServiceAccount: - description: '1Password service account token' - required: true - apiToken: - description: 'Cloudflare API token' - required: true - accountId: - description: 'Cloudflare account ID' - required: true - environment: - description: 'Cloudflare environment to deploy to (staging, production, preview)' - required: true - deploy: - description: 'Deploy as main version for all traffic instead of uploading versions' - required: true - commitTag: - description: 'Commit branch to associate with the deployment' - required: true - commitMessage: - description: 'Commit message to associate with the deployment' - required: true + opItem: + description: '1Password item to load secrets from' + required: true + opServiceAccount: + description: '1Password service account token' + required: true + apiToken: + description: 'Cloudflare API token' + required: true + accountId: + description: 'Cloudflare account ID' + required: true + environment: + description: 'Cloudflare environment to deploy to (staging, production, preview)' + required: true + deploy: + description: 'Deploy as main version for all traffic instead of uploading versions' + required: true + commitTag: + description: 'Commit branch to associate with the deployment' + required: true + commitMessage: + description: 'Commit message to associate with the deployment' + required: true outputs: deployment-url: - description: "Deployment URL" - value: ${{ steps.upload_middleware.outputs.deployment-url }} + description: 'Deployment URL' + value: ${{ steps.upload_middleware.outputs.deployment-url }} runs: using: 'composite' steps: @@ -42,33 +42,33 @@ runs: - name: Load secret uses: 1password/load-secrets-action@v2 env: - OP_SERVICE_ACCOUNT_TOKEN: ${{ inputs.opServiceAccount }} - GITBOOK_URL: ${{ inputs.opItem }}/GITBOOK_URL - GITBOOK_ICONS_URL: ${{ inputs.opItem }}/GITBOOK_ICONS_URL - GITBOOK_ICONS_TOKEN: ${{ inputs.opItem }}/GITBOOK_ICONS_TOKEN - NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ inputs.opItem }}/NEXT_SERVER_ACTIONS_ENCRYPTION_KEY - GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET - GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL - GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL - GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL - GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN - GITBOOK_OAUTH_SERVER_URL: ${{ inputs.opItem }}/GITBOOK_OAUTH_SERVER_URL - GITBOOK_SITE_OAUTH_SIGNING_SECRET: ${{ inputs.opItem }}/GITBOOK_SITE_OAUTH_SIGNING_SECRET - GITBOOK_PREVIEW_BASE_URL: ${{ inputs.opItem }}/GITBOOK_PREVIEW_BASE_URL - GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST - GITBOOK_INTEGRATIONS_CONTENT_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_CONTENT_HOST - GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY - GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL_2 - GITBOOK_IMAGE_RESIZE_SALT: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SALT - GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE - GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX - GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL + OP_SERVICE_ACCOUNT_TOKEN: ${{ inputs.opServiceAccount }} + GITBOOK_URL: ${{ inputs.opItem }}/GITBOOK_URL + GITBOOK_ICONS_URL: ${{ inputs.opItem }}/GITBOOK_ICONS_URL + GITBOOK_ICONS_TOKEN: ${{ inputs.opItem }}/GITBOOK_ICONS_TOKEN + NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ inputs.opItem }}/NEXT_SERVER_ACTIONS_ENCRYPTION_KEY + GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET + GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL + GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL + GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL + GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN + GITBOOK_OAUTH_SERVER_URL: ${{ inputs.opItem }}/GITBOOK_OAUTH_SERVER_URL + GITBOOK_SITE_OAUTH_SIGNING_SECRET: ${{ inputs.opItem }}/GITBOOK_SITE_OAUTH_SIGNING_SECRET + GITBOOK_PREVIEW_BASE_URL: ${{ inputs.opItem }}/GITBOOK_PREVIEW_BASE_URL + GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST + GITBOOK_INTEGRATIONS_CONTENT_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_CONTENT_HOST + GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY + GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL_2 + GITBOOK_IMAGE_RESIZE_SALT: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SALT + GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE + GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX + GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL - name: Build worker run: bun run turbo build:cloudflare env: - GITBOOK_RUNTIME: cloudflare - GITBOOK_BLOCK_SEARCH_INDEXATION: ${{ inputs.environment == 'preview' && 'true' || '' }} - GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE: ${{ inputs.environment == 'preview' && 'true' || '' }} + GITBOOK_RUNTIME: cloudflare + GITBOOK_BLOCK_SEARCH_INDEXATION: ${{ inputs.environment == 'preview' && 'true' || '' }} + GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE: ${{ inputs.environment == 'preview' && 'true' || '' }} shell: bash - name: Upload the DO worker @@ -135,11 +135,10 @@ runs: middlewareVersionId: ${{ steps.extract_middleware_version_id.outputs.version_id }} deploy: ${{ inputs.deploy }} - - name: Outputs shell: bash env: - DEPLOYMENT_URL: ${{ steps.upload_middleware.outputs.deployment-url }} + DEPLOYMENT_URL: ${{ steps.upload_middleware.outputs.deployment-url }} run: | echo "URL: ${{ steps.upload_middleware.outputs.deployment-url }}" - echo "Output server: ${{ steps.upload_server.outputs.command-output }}" \ No newline at end of file + echo "Output server: ${{ steps.upload_server.outputs.command-output }}" diff --git a/.github/composite/deploy-vercel/action.yaml b/.github/composite/deploy-vercel/action.yaml index 974e9c980..4aa9fbbfe 100644 --- a/.github/composite/deploy-vercel/action.yaml +++ b/.github/composite/deploy-vercel/action.yaml @@ -1,31 +1,31 @@ name: 'Deploy vercel' description: 'Deploy GitBook to Vercel' inputs: - vercelOrg: - description: 'Vercel organization' - required: true - vercelProject: - description: 'Vercel project' - required: true - vercelToken: - description: 'Vercel token' - required: true - opItem: - description: '1Password item to load secrets from' - required: true - opServiceAccount: - description: '1Password service account token' - required: true - environment: - description: 'Environment to deploy to' - required: true - headSha: - description: 'Git ref to deploy, used for the deploymentId' - required: false + vercelOrg: + description: 'Vercel organization' + required: true + vercelProject: + description: 'Vercel project' + required: true + vercelToken: + description: 'Vercel token' + required: true + opItem: + description: '1Password item to load secrets from' + required: true + opServiceAccount: + description: '1Password service account token' + required: true + environment: + description: 'Environment to deploy to' + required: true + headSha: + description: 'Git ref to deploy, used for the deploymentId' + required: false outputs: deployment-url: - description: "Deployment URL" - value: ${{ steps.deploy.outputs.deployment-url }} + description: 'Deployment URL' + value: ${{ steps.deploy.outputs.deployment-url }} runs: using: 'composite' steps: @@ -40,51 +40,51 @@ runs: run: bun run vercel pull --yes --environment=${{ inputs.environment }} --token=${{ inputs.vercelToken }} shell: bash env: - VERCEL_ORG_ID: ${{ inputs.vercelOrg }} - VERCEL_PROJECT_ID: ${{ inputs.vercelProject }} + VERCEL_ORG_ID: ${{ inputs.vercelOrg }} + VERCEL_PROJECT_ID: ${{ inputs.vercelProject }} - name: Load secret uses: 1password/load-secrets-action@v2 env: - OP_SERVICE_ACCOUNT_TOKEN: ${{ inputs.opServiceAccount }} - GITBOOK_URL: ${{ inputs.opItem }}/GITBOOK_URL - GITBOOK_ICONS_URL: ${{ inputs.opItem }}/GITBOOK_ICONS_URL - GITBOOK_ICONS_TOKEN: ${{ inputs.opItem }}/GITBOOK_ICONS_TOKEN - GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET - GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL - GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL - GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL - GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN - GITBOOK_OAUTH_SERVER_URL: ${{ inputs.opItem }}/GITBOOK_OAUTH_SERVER_URL - GITBOOK_SITE_OAUTH_SIGNING_SECRET: ${{ inputs.opItem }}/GITBOOK_SITE_OAUTH_SIGNING_SECRET - GITBOOK_PREVIEW_BASE_URL: ${{ inputs.opItem }}/GITBOOK_PREVIEW_BASE_URL - GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST - GITBOOK_INTEGRATIONS_CONTENT_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_CONTENT_HOST - GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY - GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL_2 - GITBOOK_IMAGE_RESIZE_SALT: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SALT - GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE - GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX - GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL + OP_SERVICE_ACCOUNT_TOKEN: ${{ inputs.opServiceAccount }} + GITBOOK_URL: ${{ inputs.opItem }}/GITBOOK_URL + GITBOOK_ICONS_URL: ${{ inputs.opItem }}/GITBOOK_ICONS_URL + GITBOOK_ICONS_TOKEN: ${{ inputs.opItem }}/GITBOOK_ICONS_TOKEN + GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET + GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL + GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL + GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL + GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN + GITBOOK_OAUTH_SERVER_URL: ${{ inputs.opItem }}/GITBOOK_OAUTH_SERVER_URL + GITBOOK_SITE_OAUTH_SIGNING_SECRET: ${{ inputs.opItem }}/GITBOOK_SITE_OAUTH_SIGNING_SECRET + GITBOOK_PREVIEW_BASE_URL: ${{ inputs.opItem }}/GITBOOK_PREVIEW_BASE_URL + GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST + GITBOOK_INTEGRATIONS_CONTENT_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_CONTENT_HOST + GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY + GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL_2 + GITBOOK_IMAGE_RESIZE_SALT: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SALT + GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE + GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX + GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL - name: Inject build env vars if: ${{ inputs.environment == 'preview' }} shell: bash run: | - HEAD_SHA=$(git rev-parse HEAD) - echo "resolved HEAD_SHA: $HEAD_SHA" - echo "GITBOOK_HEAD_SHA=$HEAD_SHA" >> .vercel/.env.${{ inputs.environment }}.local - echo "GITBOOK_RUNTIME=vercel" >> .vercel/.env.${{ inputs.environment }}.local - echo "GITBOOK_BLOCK_SEARCH_INDEXATION=true" >> .vercel/.env.${{ inputs.environment }}.local - echo "GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE=true" >> .vercel/.env.${{ inputs.environment }}.local - echo "--- .vercel/.env.${{ inputs.environment }}.local after inject ---" - cat .vercel/.env.${{ inputs.environment }}.local + HEAD_SHA=$(git rev-parse HEAD) + echo "resolved HEAD_SHA: $HEAD_SHA" + echo "GITBOOK_HEAD_SHA=$HEAD_SHA" >> .vercel/.env.${{ inputs.environment }}.local + echo "GITBOOK_RUNTIME=vercel" >> .vercel/.env.${{ inputs.environment }}.local + echo "GITBOOK_BLOCK_SEARCH_INDEXATION=true" >> .vercel/.env.${{ inputs.environment }}.local + echo "GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE=true" >> .vercel/.env.${{ inputs.environment }}.local + echo "--- .vercel/.env.${{ inputs.environment }}.local after inject ---" + cat .vercel/.env.${{ inputs.environment }}.local - name: Build Project Artifacts run: bun run vercel build --target=${{ inputs.environment }} --token=${{ inputs.vercelToken }} shell: bash env: - VERCEL_ORG_ID: ${{ inputs.vercelOrg }} - VERCEL_PROJECT_ID: ${{ inputs.vercelProject }} - GITBOOK_RUNTIME: vercel - GITBOOK_HEAD_SHA: ${{ inputs.headSha }} + VERCEL_ORG_ID: ${{ inputs.vercelOrg }} + VERCEL_PROJECT_ID: ${{ inputs.vercelProject }} + GITBOOK_RUNTIME: vercel + GITBOOK_HEAD_SHA: ${{ inputs.headSha }} - name: Deploy Project Artifacts to Vercel id: deploy shell: bash @@ -92,10 +92,9 @@ runs: DEPLOYMENT_URL=$(bun run vercel deploy --prebuilt --target=${{ inputs.environment }} --token=${{ inputs.vercelToken }}) echo "deployment-url=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT" env: - VERCEL_ORG_ID: ${{ inputs.vercelOrg }} - VERCEL_PROJECT_ID: ${{ inputs.vercelProject }} + VERCEL_ORG_ID: ${{ inputs.vercelOrg }} + VERCEL_PROJECT_ID: ${{ inputs.vercelProject }} - name: Outputs shell: bash run: | echo "URL: ${{ steps.deploy.outputs.deployment-url }}" - diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b92d860ca..a627b4546 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,6 +20,20 @@ jobs: env: PUPPETEER_SKIP_DOWNLOAD: 1 - run: bun format:check + lint: + runs-on: ubuntu-latest + name: Lint + timeout-minutes: 6 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Bun + uses: ./.github/composite/setup-bun + - name: Install dependencies + run: bun install --frozen-lockfile + env: + PUPPETEER_SKIP_DOWNLOAD: 1 + - run: bun lint test: runs-on: ubuntu-latest name: Test diff --git a/.github/workflows/deploy-preview.yaml b/.github/workflows/deploy-preview.yaml index 44269f1d1..f3518e818 100644 --- a/.github/workflows/deploy-preview.yaml +++ b/.github/workflows/deploy-preview.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.fork }} environment: - name: preview-approval + name: preview-approval steps: - name: Approval gate run: echo "Preview deployment approved." @@ -21,8 +21,8 @@ jobs: needs: approval if: ${{ always() && (needs.approval.result == 'success' || needs.approval.result == 'skipped') }} environment: - name: 2v-preview - url: ${{ steps.deploy.outputs.deployment-url }} + name: 2v-preview + url: ${{ steps.deploy.outputs.deployment-url }} outputs: deployment-url: ${{ steps.deploy.outputs.deployment-url }} steps: @@ -47,8 +47,8 @@ jobs: needs: approval if: ${{ always() && (needs.approval.result == 'success' || needs.approval.result == 'skipped') }} environment: - name: 2c-preview - url: ${{ steps.deploy.outputs.deployment-url }} + name: 2c-preview + url: ${{ steps.deploy.outputs.deployment-url }} outputs: deployment-url: ${{ steps.deploy.outputs.deployment-url || steps.extract-worker-id.outputs.worker-url }} steps: @@ -72,10 +72,10 @@ jobs: id: extract-worker-id if: ${{ !steps.deploy.outputs.deployment-url }} run: | - if [[ "${{ steps.deploy.outputs.command-output }}" =~ Worker\ Version\ ID:\ ([0-9a-f]{8})-([0-9a-f-]+) ]]; then - WORKER_ID_FIRST_PART="${BASH_REMATCH[1]}" - echo "worker-url=https://${WORKER_ID_FIRST_PART}-gitbook-open-v2-preview.gitbook.workers.dev/" >> $GITHUB_OUTPUT - fi + if [[ "${{ steps.deploy.outputs.command-output }}" =~ Worker\ Version\ ID:\ ([0-9a-f]{8})-([0-9a-f-]+) ]]; then + WORKER_ID_FIRST_PART="${BASH_REMATCH[1]}" + echo "worker-url=https://${WORKER_ID_FIRST_PART}-gitbook-open-v2-preview.gitbook.workers.dev/" >> $GITHUB_OUTPUT + fi - name: Outputs run: | echo "URL: ${{ steps.deploy.outputs.deployment-url || steps.extract-worker-id.outputs.worker-url }}" diff --git a/.github/workflows/deploy-production.yaml b/.github/workflows/deploy-production.yaml index fee8519ab..dcf465a76 100644 --- a/.github/workflows/deploy-production.yaml +++ b/.github/workflows/deploy-production.yaml @@ -7,7 +7,7 @@ jobs: deploy-v2-vercel: name: Deploy v2 to Vercel (production) runs-on: ubuntu-latest - environment: + environment: name: 2v-production url: ${{ steps.deploy.outputs.deployment-url }} outputs: @@ -19,16 +19,16 @@ jobs: id: deploy uses: ./.github/composite/deploy-vercel with: - environment: production - vercelOrg: ${{ secrets.VERCEL_ORG_ID }} - vercelProject: ${{ secrets.VERCEL_PROJECT_ID }} - vercelToken: ${{ secrets.VERCEL_TOKEN }} - opItem: op://gitbook-open/2v-production - opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + environment: production + vercelOrg: ${{ secrets.VERCEL_ORG_ID }} + vercelProject: ${{ secrets.VERCEL_PROJECT_ID }} + vercelToken: ${{ secrets.VERCEL_TOKEN }} + opItem: op://gitbook-open/2v-production + opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} deploy-v2-cloudflare: name: Deploy v2 to Cloudflare Worker (production) runs-on: ubuntu-latest - environment: + environment: name: 2c-production url: ${{ steps.deploy.outputs.deployment-url }} outputs: @@ -50,4 +50,4 @@ jobs: commitMessage: ${{ github.sha }} - name: Outputs run: | - echo "URL: ${{ steps.deploy.outputs.deployment-url }}" \ No newline at end of file + echo "URL: ${{ steps.deploy.outputs.deployment-url }}" diff --git a/.github/workflows/deploy-staging.yaml b/.github/workflows/deploy-staging.yaml index c7766a903..394ede813 100644 --- a/.github/workflows/deploy-staging.yaml +++ b/.github/workflows/deploy-staging.yaml @@ -7,7 +7,7 @@ jobs: deploy-v2-vercel: name: Deploy v2 to Vercel (staging) runs-on: ubuntu-latest - environment: + environment: name: 2v-staging url: ${{ steps.deploy.outputs.deployment-url }} outputs: @@ -19,16 +19,16 @@ jobs: id: deploy uses: ./.github/composite/deploy-vercel with: - environment: staging - vercelOrg: ${{ secrets.VERCEL_ORG_ID }} - vercelProject: ${{ secrets.VERCEL_PROJECT_ID }} - vercelToken: ${{ secrets.VERCEL_TOKEN }} - opItem: op://gitbook-open/2v-staging - opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + environment: staging + vercelOrg: ${{ secrets.VERCEL_ORG_ID }} + vercelProject: ${{ secrets.VERCEL_PROJECT_ID }} + vercelToken: ${{ secrets.VERCEL_TOKEN }} + opItem: op://gitbook-open/2v-staging + opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} deploy-v2-cloudflare: name: Deploy v2 to Cloudflare Worker (staging) runs-on: ubuntu-latest - environment: + environment: name: 2c-staging url: ${{ steps.deploy.outputs.deployment-url }} outputs: @@ -50,4 +50,4 @@ jobs: commitMessage: ${{ github.sha }} - name: Outputs run: | - echo "URL: ${{ steps.deploy.outputs.deployment-url }}" \ No newline at end of file + echo "URL: ${{ steps.deploy.outputs.deployment-url }}" diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 68bbbdc0b..3316674b6 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -4,7 +4,7 @@ on: push: branches: - main - + concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 000000000..094382322 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,70 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 100, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "jsxSingleQuote": false, + "quoteProps": "as-needed", + "trailingComma": "es5", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "always", + "endOfLine": "lf", + "sortImports": { + "customGroups": [ + { + "groupName": "workspace", + "elementNamePattern": ["@gitbook/**"] + } + ], + "groups": [ + ["value-builtin", "value-external", "type-builtin", "type-external"], + { "newlinesBetween": true }, + "workspace", + { "newlinesBetween": true }, + [ + "value-internal", + "type-internal", + "value-parent", + "type-parent", + "value-sibling", + "type-sibling", + "value-index", + "type-index" + ], + "type-import", + "unknown" + ] + }, + "sortPackageJson": false, + "sortTailwindcss": { + "config": "./packages/gitbook/tailwind.config.ts", + "attributes": ["class", "className", "style"], + "functions": ["clsx", "tw"] + }, + "ignorePatterns": [ + "**/node_modules/**", + "**/dist/**", + "**/build/**", + "**/public/**", + "**/.next/**", + "**/.open-next/**", + "**/.turbo/**", + "**/.vercel/**", + "**/.cache/**", + "**/.wrangler/**", + "**/*.log", + "**/*.MD", + "**/*.md", + "**/*.mdx", + "**/*.html", + "**/*.css", + "packages/embed/standalone/**", + "packages/openapi-parser/src/fixtures/**", + "packages/emoji-codepoints/index.ts", + "packages/icons/src/data/*.json", + "packages/gitbook/worker-configuration.d.ts" + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 000000000..f77105a94 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,87 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "categories": { + "correctness": "warn", + "suspicious": "warn", + "perf": "warn" + }, + "plugins": ["eslint", "typescript", "unicorn", "oxc", "react", "jsx-a11y", "vitest"], + "env": { + "builtin": true, + "browser": true, + "node": true, + "shared-node-browser": true, + "worker": true + }, + "globals": { + "Bun": "readonly", + "GitBookIntegrationEvent": "readonly", + "React": "readonly" + }, + "rules": { + "eslint/no-console": [ + "warn", + { + "allow": ["assert", "error", "warn"] + } + ], + "eslint/no-control-regex": "warn", + "eslint/no-prototype-builtins": "warn", + "eslint/no-cond-assign": "warn", + "eslint/no-undef": "error", + "eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_", + "ignoreRestSiblings": true, + "varsIgnorePattern": "^_" + } + ], + "react/button-has-type": "warn", + "react/exhaustive-deps": "warn", + "react/jsx-key": "warn", + "react/jsx-no-useless-fragment": "warn", + "react/no-array-index-key": "warn", + "react/void-dom-elements-no-children": "warn", + "react/rules-of-hooks": "error", + "jsx-a11y/alt-text": "warn", + "jsx-a11y/anchor-is-valid": "warn", + "jsx-a11y/click-events-have-key-events": "warn", + "jsx-a11y/iframe-has-title": "warn", + "jsx-a11y/interactive-supports-focus": "warn", + "jsx-a11y/label-has-associated-control": "warn", + "jsx-a11y/no-noninteractive-tabindex": "warn", + "jsx-a11y/role-has-required-aria-props": "warn", + "jsx-a11y/tabindex-no-positive": "warn", + "typescript/array-type": "error", + "typescript/no-explicit-any": "warn", + "typescript/no-non-null-assertion": "warn", + "typescript/no-confusing-void-expression": "warn", + "typescript/only-throw-error": "error", + "vitest/no-focused-tests": "error", + "no-delete-var": "warn", + "no-debugger": "error", + "no-throw-literal": "error", + "use-isnan": "error", + "valid-typeof": "error" + }, + "ignorePatterns": [ + "**/node_modules/**", + "**/dist/**", + "**/build/**", + "**/public/**", + "**/.next/**", + "**/.open-next/**", + "**/.turbo/**", + "**/.vercel/**", + "**/.cache/**", + "**/.wrangler/**", + "packages/embed/standalone/**", + "packages/openapi-parser/src/fixtures/**", + "packages/emoji-codepoints/index.ts", + "packages/icons/src/data/*.json", + "packages/gitbook/worker-configuration.d.ts", + "**/*.css" + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5f44864b4..9d5eb3d7f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["biomejs.biome"] + "recommendations": ["oxc.oxc-vscode"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 8a86e5d5d..b0966a99e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,14 +8,12 @@ ["style \\=([^;]*);", "\\`([^\\`]*)\\`"] ], "tailwindCSS.classAttributes": ["class", "className", "style", ".*Style"], - "prettier.enable": false, "editor.formatOnSave": true, - "editor.defaultFormatter": "biomejs.biome", + "editor.defaultFormatter": "oxc.oxc-vscode", "editor.codeActionsOnSave": { - "source.organizeImports.biome": "explicit", - "source.fixAll.biome": "explicit" + "source.fixAll.oxc": "always" }, "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "oxc.oxc-vscode" } } diff --git a/AGENTS.md b/AGENTS.md index 0dd765268..f88860b3f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,8 @@ bun install # Install dependencies bun dev # Start dev server (all packages) bun run build # Build all packages -bun run format # Format with Biome (run after every change) +bun run lint # Lint with Oxlint +bun run format # Format with Oxfmt (run after every change) bun run typecheck # Type-check all packages bun run unit # Run unit tests ``` @@ -74,7 +75,7 @@ Save as `.changeset/.md`, then commit it separately with message: `changes ## Formatting -Uses Biome (not ESLint/Prettier). Always run `bun run format` before committing. +Linting uses Oxlint and formatting uses Oxfmt. Always run `bun run format` before committing. ## Comments diff --git a/biome.json b/biome.json deleted file mode 100644 index fc877aa01..000000000 --- a/biome.json +++ /dev/null @@ -1,178 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "ignore": [ - "**/node_modules/**/*", - "**/dist/**/*", - "**/build/**/*", - "**/public/**/*", - "**/.next/**/*", - "**/.open-next/**/*", - "**/.turbo/**/*", - "**/.vercel/**/*", - "**/.cache/**/*", - "**/.wrangler/**/*", - "packages/embed/standalone/**/*", - "packages/openapi-parser/src/fixtures/**/*", - "packages/emoji-codepoints/index.ts", - "packages/icons/src/data/*.json", - "packages/gitbook/worker-configuration.d.ts", - "gitbook/tsconfig.json", - "**/*.css" - ] - }, - "formatter": { - "enabled": true, - "useEditorconfig": true, - "formatWithErrors": false, - "indentStyle": "space", - "indentWidth": 4, - "lineEnding": "lf", - "lineWidth": 100, - "attributePosition": "auto", - "bracketSpacing": true - }, - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "performance": { - "noDelete": "warn" - }, - "security": { - "noDangerouslySetInnerHtml": "off" - }, - "complexity": { - "noForEach": "off", - "noUselessFragments": "warn", - "noBannedTypes": "warn" - }, - "correctness": { - "noUndeclaredVariables": "error", - "noUnusedVariables": "error", - "useArrayLiterals": "error", - "useHookAtTopLevel": "error", - "noUnusedImports": "error", - "noVoidElementsWithChildren": "warn", - "useJsxKeyInIterable": "warn", - "useExhaustiveDependencies": "warn", - "noUnknownFunction": "warn" - }, - "style": { - "noNonNullAssertion": "warn", - "noParameterAssign": "off", - "useThrowOnlyError": "error" - }, - "suspicious": { - "noConsole": { - "level": "warn", - "options": { - "allow": ["assert", "error", "warn"] - } - }, - "noExplicitAny": "warn", - "noImplicitAnyLet": "warn", - "noConfusingVoidType": "warn", - "noControlCharactersInRegex": "warn", - "noPrototypeBuiltins": "warn", - "noAssignInExpressions": "warn", - "noArrayIndexKey": "warn" - }, - "a11y": { - "useSemanticElements": "warn", - "useKeyWithClickEvents": "warn", - "noSvgWithoutTitle": "warn", - "useButtonType": "warn", - "useIframeTitle": "warn", - "useAltText": "warn", - "noPositiveTabindex": "warn", - "useFocusableInteractive": "warn", - "useAriaPropsForRole": "warn", - "useValidAnchor": "warn", - "noLabelWithoutControl": "warn", - "noNoninteractiveTabindex": "warn" - }, - "nursery": { - "useSortedClasses": { - "level": "error", - "fix": "safe", - "options": { - "attributes": ["class", "className", "style"], - "functions": ["clsx", "tw"] - } - } - } - } - }, - "javascript": { - "formatter": { - "jsxQuoteStyle": "double", - "quoteProperties": "asNeeded", - "trailingCommas": "es5", - "semicolons": "always", - "arrowParentheses": "always", - "bracketSameLine": false, - "quoteStyle": "single", - "attributePosition": "auto", - "bracketSpacing": true - } - }, - "overrides": [ - { - "include": [ - "packages/gitbook/**/*", - "packages/react-openapi/**/*", - "packages/react-math/**/*", - "packages/react-contentkit/**/*", - "packages/icons/**/*" - ], - "javascript": { - "globals": ["React"] - } - }, - { - "include": ["packages/gitbook/**/*"], - "javascript": { - "globals": ["React", "GitBookIntegrationEvent"] - } - }, - { - "include": ["*.css"], - "javascript": { - "globals": ["theme"] - } - }, - { - "include": ["*.test.ts", "packages/gitbook/tests/**/*"], - "javascript": { - "globals": ["Bun"] - } - }, - { - "include": [ - "packages/cache-do/**/*", - "packages/gitbook/cf-env.d.ts", - "packages/gitbook/src/cloudflare-entrypoint.ts" - ], - "javascript": { - "globals": [ - "DurableObjectLocationHint", - "DurableObjectNamespace", - "DurableObjectStub", - "ContinentCode", - "Fetcher", - "ExportedHandler" - ] - } - } - ] -} diff --git a/bun.lock b/bun.lock index b6f2bd2f7..e63aebac1 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,9 @@ "": { "name": "gitbook", "devDependencies": { - "@biomejs/biome": "^1.9.4", "@changesets/cli": "^2.31.0", + "oxfmt": "^0.62.0", + "oxlint": "^1.77.0", "turbo": "^2.10.3", "vercel": "50.37.3", }, @@ -538,24 +539,6 @@ "@base-ui/utils": ["@base-ui/utils@0.3.2", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.12", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ=="], - "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], - "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], "@bytecodealliance/preview2-shim": ["@bytecodealliance/preview2-shim@0.17.6", "", {}, "sha512-n3cM88gTen5980UOBAD6xDcNNL3ocTK8keab21bpx1ONdA+ARj7uD1qoFxOWCyKlkpSi195FH+GeAut7Oc6zZw=="], @@ -1028,6 +1011,82 @@ "@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.111.0", "", { "os": "win32", "cpu": "x64" }, "sha512-QddKW4kBH0Wof6Y65eYCNHM4iOGmCTWLLcNYY1FGswhzmTYOUVXajNROR+iCXAOFnOF0ldtsR79SyqgyHH1Bgg=="], + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.62.0", "", { "os": "android", "cpu": "arm" }, "sha512-pdsv0C4gPjJ8H1+sd8u0BDx+yLACTL+rgeMIOL1ln4ihSnhw8CWXtYWgvcSkyTfgGBIzFKab+d8rx9Xl4en/Kw=="], + + "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.62.0", "", { "os": "android", "cpu": "arm64" }, "sha512-WC3YQ7uS/KtDrjmqwBviwFKe9qeoi+eXx8aX1z/ffG23Md75myjrJaQqTuJvdOLPoa4EYTjDWH0dHXfwulCVog=="], + + "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.62.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GM8Yf3LjjaR1I8PD0SfeoIlwhsh9GvSF+cQ8sf624Yxnjsyumn95aFzYfKJVefblfDIiOAnZ7QVm2sa21Er/0Q=="], + + "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.62.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-d5THp7F8bCxLqNogEXDORRsQD6dosf3EyFtnXfBer6v+8tGdcWIjoDX9WaXrrF/26zOmL8qHpPTKCEvpBDmZkQ=="], + + "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.62.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1DnrtXGZooOZ0fHgAXZUaDQzBVh1CM2MNW4oBXyQ2aWKvCHjyljvT9fgBkOM0fEOb96X5eqtcfJ0YUVt9jj66g=="], + + "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.62.0", "", { "os": "linux", "cpu": "arm" }, "sha512-4pQDHOYRH+Huqe0StIaWyvk2CVl/aTaqSrbZpA3/pLS2xH24ME7lBgYprhQF2fRkHBzhGGGKliwxFsDdHwx59g=="], + + "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.62.0", "", { "os": "linux", "cpu": "arm" }, "sha512-X0jAaZJFMCVKhB6YyWVTQ/wN2DLsBcZKSMqTS76bF6riT+XZdtg2FPEdjDvdVbunO9cG+tWiVaEs4Zs38lxYog=="], + + "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.62.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-682Z8T5s8T5ATArYtsejKvbIfd8LEAXyyDkKkoZVq8HND7Vx8TYLlrDjDSeYfodMeVwHOgkj13lJYR8cj6vUSg=="], + + "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.62.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lk25fAl7KWaLWVJcW0CHEXB7QlQZtx5eDkjpaGMK0hzXTjUe0Wmlu8IKuFHoviSOcEJedRTs4VE/506VqGxGew=="], + + "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.62.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-SFyNqHQLwySceWNLhiSldx7wPXRAzP0L0WcW9GegP3uWrpZGJiZlQO85NbHAFPEfxR9PhZ9qSnZryEh7+v+4Gw=="], + + "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.62.0", "", { "os": "linux", "cpu": "none" }, "sha512-KYj55C1ywJfHo6+aKDuEmUtVEdJALsC5GwayDGsI6FGz2GxFqNr/mA8nxVsNbJzm7sE5MRqTQ9ziImSzhYXysA=="], + + "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.62.0", "", { "os": "linux", "cpu": "none" }, "sha512-BhZDNo5GOU5nC378RhD0/XpvaEBHsH3HLgJp8YZX3A0InC7oivzA63HsRmiXFLtLSHAstEVrDf6fbC7Rs8Jh/A=="], + + "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.62.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-UyAFmyHkgSgUJ/wOM4p3U8AC2yAFvRH5PNBs7TnK0fObTT/XSWcdr/lAzPSWaekHaZFaMeFZyk9n93Joq3J93A=="], + + "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.62.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1iYMP0leytWazFubD/WnINJuIrzRPuoL1aWEJdlGezEzDbTxcd29R4r8IUzP2oWeKst5V02uMJgR2NILlPlG6w=="], + + "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.62.0", "", { "os": "linux", "cpu": "x64" }, "sha512-4rA/URtJSTVNVAQz6Q8wf7SaRvOXVy+TizriT9hs/Y1XhLR/R+92uWKRQG8yFWRAIEBbFHJ6WevQcl/G9SXEfw=="], + + "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.62.0", "", { "os": "none", "cpu": "arm64" }, "sha512-mSZuFHU2ar1KLUjXpI2QBQcJ1VsOB3mOCgQXuXCpKs19dgh4u+OaovNfrWDfiJb+ihJ2+f7YFcaO9bS2dlTCXA=="], + + "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.62.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-OfwuhkcjDlqC4EgDojtiV9mzpLqeB9KqTOWPOjLEYBVdDCVSxqW3qzp/xcIxsbtI0UgGCnKvAqYKyY25kf5JZw=="], + + "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.62.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-P9uDDNFRzghO3X8QAzhkjKhK7JvtABsVn8UYtFX7uor12IAnwNt8nNIctvfWj1JkQU/kE+fmLRPiw7XlrIHsZw=="], + + "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.62.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dlI5SY7XYQCiCBafntWagCR6HcAJB/NpsLtdlPx8x08+Osz8Ok1HHz1GZuusegCe/VoJ6pAnF5a4pd5OZAq7qQ=="], + + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.78.0", "", { "os": "android", "cpu": "arm" }, "sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.78.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CDfxZgB61B7buRdY2FJoAYYPPXCZ1EoC1LKscnC5dg3kjobdxiconvAvvN1BmHyW4PyFT3jRLDag/BY/roSNBQ=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.78.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2Y2U9Ahrz+OO0Ej88f9SJYq51/jUBp1Mc7iZu0ukrbeeZ3gpRGfzIFnoqfHDY96xr0GEfNrPUBFEy0nN5aD7HA=="], + + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.78.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-rpych6eJq6m9jDRypTEaPD1xysaEW5h9+xuxhGK/QhOg+/xaqPZrCrTNoIl/f3nEjuJeCEmstNDlrE9rJi/3/g=="], + + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.78.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IcMGrQT3QizkOESUJd5et+rOhVqSkNDfNik1cvrKDqIbzqx9KMtRswpFgkCuNTSwylCFLKhGUu8KmqY1ZnC0Dg=="], + + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.78.0", "", { "os": "linux", "cpu": "arm" }, "sha512-/uLdoJ0IXE6vo/0f0LKjinQAp+re+VMaCWaNT8ENIv2EOCkSsc8SGaflXAuW0Jua2dq5+GLVWm1NQK7P3UFSNQ=="], + + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.78.0", "", { "os": "linux", "cpu": "arm" }, "sha512-7xi4Wb/O8NRJhLoUXmDJMUVpNYvB5kefdhFU1Jb8rtae4QoXlTiLwI14X4YvAXVZLNZChP8m5qO9SQAlWQTbkQ=="], + + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.78.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-4hFW0+fVXa3OIh1Y4A5SPkmvI4wuuBSrCVKzOyE7PTjhc7yEqZ1pmvEEeS5Lj/MaqvegFxXyF33N+6jkehxdyg=="], + + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.78.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-oC0mvsgBJjlMijSDEhx9KuvR9zYeHXceA9MjbuXB1F8NSR78Yj2unOBrstEvTVaq+pko+kuue6DajC00eqvTdg=="], + + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.78.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-XAllT5SUZS+ohjuZ3/5S0cwe0r7eboiuigeStCZ5DXRYx/2KVM2UvQXvAfyzXEimtQjAB7cDQ2YxDe2Zl2WNQQ=="], + + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.78.0", "", { "os": "linux", "cpu": "none" }, "sha512-trucMER/0QtecoXvc1y/UVqE3kwJipDwrx4oHfj+nNm3dq2zjP44WT0CfHNDPM3G1DXIkx/gY6lAD21NSCZVhA=="], + + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.78.0", "", { "os": "linux", "cpu": "none" }, "sha512-cm3O4F/HQbdzOUX5mKHqG5KDL6E5w0pnlZ+fbBy2rmLryPOowkuLagFHTopQsEIpjcaZoPOrL+BmmAytAG9HFg=="], + + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.78.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-33wRf6HqGNsybJ3qX4cGaQN2ODPxNmc1rMa0mrTmx3eFq1VzOnvQooi9bIGVYakW8a/wmqVx1mgsUm8R2xfTiw=="], + + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.78.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rRdISSYegj6VganMZ9tjRjijowfHJ09IZU01i0toBAqr6n5LEtwHq2IeS4FjW2RoskOHlb6efB26H5izYb3GEQ=="], + + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.78.0", "", { "os": "linux", "cpu": "x64" }, "sha512-GmsP4rW0xTL6u5CVdcDsaN5Fbc7hBc382Wmar1kttbnwSEviM+rSINKOMQ+UQ6iH+AGwC+8gaAiwu134Tgh6Lg=="], + + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.78.0", "", { "os": "none", "cpu": "arm64" }, "sha512-sy9yeYuADc8a+n4TLBayzMCZiHPW78DcIFVpOXTmdKHWQeM9xe5uzkqIIZmi326D5hY9XVwacipEB1p7tQjPAg=="], + + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.78.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-rjc2hF1KfMi8fZj1X/m3AmnHbdsF3rL0v6KQg0Uc880Yb2khjz+3U14sfdZ7jWTpRnN1m1NQa/TT7uU9lJWPrA=="], + + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.78.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zcuXFVrEFHIafRfkCQT8w/Xe41o07ozl/vwHq7p94vB29xVzsB0sZGYORU1jhcYKv3Lr0J3HbJ2T4fHH5rWmvA=="], + + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.78.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA=="], + "@panzoom/panzoom": ["@panzoom/panzoom@4.6.1", "", {}, "sha512-ogf/KhHHjj+DYAvHfaf3TXMQ8OE36pJtKpabLlx1OmpjcgtpCvkUoCiNONA8kuVRPGJdLiqMf0n8LRFXj1OyuA=="], "@phosphor-icons/core": ["@phosphor-icons/core@2.1.1", "", {}, "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ=="], @@ -2934,6 +2993,10 @@ "oxc-transform": ["oxc-transform@0.111.0", "", { "optionalDependencies": { "@oxc-transform/binding-android-arm-eabi": "0.111.0", "@oxc-transform/binding-android-arm64": "0.111.0", "@oxc-transform/binding-darwin-arm64": "0.111.0", "@oxc-transform/binding-darwin-x64": "0.111.0", "@oxc-transform/binding-freebsd-x64": "0.111.0", "@oxc-transform/binding-linux-arm-gnueabihf": "0.111.0", "@oxc-transform/binding-linux-arm-musleabihf": "0.111.0", "@oxc-transform/binding-linux-arm64-gnu": "0.111.0", "@oxc-transform/binding-linux-arm64-musl": "0.111.0", "@oxc-transform/binding-linux-ppc64-gnu": "0.111.0", "@oxc-transform/binding-linux-riscv64-gnu": "0.111.0", "@oxc-transform/binding-linux-riscv64-musl": "0.111.0", "@oxc-transform/binding-linux-s390x-gnu": "0.111.0", "@oxc-transform/binding-linux-x64-gnu": "0.111.0", "@oxc-transform/binding-linux-x64-musl": "0.111.0", "@oxc-transform/binding-openharmony-arm64": "0.111.0", "@oxc-transform/binding-wasm32-wasi": "0.111.0", "@oxc-transform/binding-win32-arm64-msvc": "0.111.0", "@oxc-transform/binding-win32-ia32-msvc": "0.111.0", "@oxc-transform/binding-win32-x64-msvc": "0.111.0" } }, "sha512-oa5KKSDNLHZGaiqIGAbCWXeN9IJUAz9MElWcQX90epDxdKc9Hrt/BsLj3K4gDqfAYa5dwdH+ZCFJG9hR74fiGg=="], + "oxfmt": ["oxfmt@0.62.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.62.0", "@oxfmt/binding-android-arm64": "0.62.0", "@oxfmt/binding-darwin-arm64": "0.62.0", "@oxfmt/binding-darwin-x64": "0.62.0", "@oxfmt/binding-freebsd-x64": "0.62.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.62.0", "@oxfmt/binding-linux-arm-musleabihf": "0.62.0", "@oxfmt/binding-linux-arm64-gnu": "0.62.0", "@oxfmt/binding-linux-arm64-musl": "0.62.0", "@oxfmt/binding-linux-ppc64-gnu": "0.62.0", "@oxfmt/binding-linux-riscv64-gnu": "0.62.0", "@oxfmt/binding-linux-riscv64-musl": "0.62.0", "@oxfmt/binding-linux-s390x-gnu": "0.62.0", "@oxfmt/binding-linux-x64-gnu": "0.62.0", "@oxfmt/binding-linux-x64-musl": "0.62.0", "@oxfmt/binding-openharmony-arm64": "0.62.0", "@oxfmt/binding-win32-arm64-msvc": "0.62.0", "@oxfmt/binding-win32-ia32-msvc": "0.62.0", "@oxfmt/binding-win32-x64-msvc": "0.62.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-vxgGHTmnDU9j4CX7dDBLzxgmHxfda/yPcgJkGCMUSCwRmz+euo/V08xXLNgXTeqAB9Fhf3Pe2nO1RNKLCVgphQ=="], + + "oxlint": ["oxlint@1.78.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.78.0", "@oxlint/binding-android-arm64": "1.78.0", "@oxlint/binding-darwin-arm64": "1.78.0", "@oxlint/binding-darwin-x64": "1.78.0", "@oxlint/binding-freebsd-x64": "1.78.0", "@oxlint/binding-linux-arm-gnueabihf": "1.78.0", "@oxlint/binding-linux-arm-musleabihf": "1.78.0", "@oxlint/binding-linux-arm64-gnu": "1.78.0", "@oxlint/binding-linux-arm64-musl": "1.78.0", "@oxlint/binding-linux-ppc64-gnu": "1.78.0", "@oxlint/binding-linux-riscv64-gnu": "1.78.0", "@oxlint/binding-linux-riscv64-musl": "1.78.0", "@oxlint/binding-linux-s390x-gnu": "1.78.0", "@oxlint/binding-linux-x64-gnu": "1.78.0", "@oxlint/binding-linux-x64-musl": "1.78.0", "@oxlint/binding-openharmony-arm64": "1.78.0", "@oxlint/binding-win32-arm64-msvc": "1.78.0", "@oxlint/binding-win32-ia32-msvc": "1.78.0", "@oxlint/binding-win32-x64-msvc": "1.78.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-QgQePuxIqKOzo1KSjG2EnITEeWvWnKAm77eq8nrMtf6AGoA+zyGc4PFYtDNJSD25g/ibOwfQ851hZ4/SPkMVoA=="], + "p-filter": ["p-filter@2.1.0", "", { "dependencies": { "p-map": "^2.0.0" } }, "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw=="], "p-finally": ["p-finally@2.0.1", "", {}, "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw=="], @@ -3336,6 +3399,8 @@ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], + "tldts": ["tldts@7.0.30", "", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="], "tldts-core": ["tldts-core@7.0.30", "", {}, "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q=="], diff --git a/package.json b/package.json index 46fd82c99..33ec5a708 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "node": "^22.3.0" }, "devDependencies": { - "@biomejs/biome": "^1.9.4", "@changesets/cli": "^2.31.0", + "oxfmt": "^0.62.0", + "oxlint": "^1.77.0", "turbo": "^2.10.3", "vercel": "50.37.3" }, @@ -27,8 +28,10 @@ "build": "turbo run build", "clean-deps": "rm -rf node_modules && rm -rf packages/*/node_modules", "typecheck": "turbo run typecheck", - "format": "biome check --write ./", - "format:check": "biome check --diagnostic-level=error ./", + "lint": "oxlint --quiet", + "lint:fix": "oxlint --fix --quiet", + "format": "oxfmt", + "format:check": "oxfmt --check", "unit": "turbo run unit", "e2e": "turbo run e2e", "e2e-customers": "turbo run e2e-customers", @@ -39,7 +42,9 @@ "clean": "turbo run clean" }, "workspaces": { - "packages": ["packages/*"], + "packages": [ + "packages/*" + ], "catalog": { "@tsconfig/strictest": "^2.0.6", "@tsconfig/node20": "^20.1.6", diff --git a/packages/browser-types/package.json b/packages/browser-types/package.json index 7516cc174..d30c85780 100644 --- a/packages/browser-types/package.json +++ b/packages/browser-types/package.json @@ -25,7 +25,11 @@ "dev": "bun run build -- --watch ./src", "publish-to-npm": "../../scripts/publish-if-new.sh" }, - "files": ["dist", "README.md", "CHANGELOG.md"], + "files": [ + "dist", + "README.md", + "CHANGELOG.md" + ], "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" diff --git a/packages/cache-tags/package.json b/packages/cache-tags/package.json index 60d658ad7..bd3e27624 100644 --- a/packages/cache-tags/package.json +++ b/packages/cache-tags/package.json @@ -24,7 +24,11 @@ "dev": "bun run build -- --watch ./src", "publish-to-npm": "../../scripts/publish-if-new.sh" }, - "files": ["dist", "README.md", "CHANGELOG.md"], + "files": [ + "dist", + "README.md", + "CHANGELOG.md" + ], "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" diff --git a/packages/cache-tags/src/index.ts b/packages/cache-tags/src/index.ts index 072d7f00a..99e641256 100644 --- a/packages/cache-tags/src/index.ts +++ b/packages/cache-tags/src/index.ts @@ -1,14 +1,15 @@ -import type { ComputedContentSource } from '@gitbook/api'; import assertNever from 'assert-never'; +import type { ComputedContentSource } from '@gitbook/api'; + /** * Get a stringified cache tag for a given object. */ export function getCacheTag( spec: /** - * All data related to a user - * @deprecated - in v2, no tag as this is an immutable data - */ + * All data related to a user + * @deprecated - in v2, no tag as this is an immutable data + */ | { tag: 'user'; user: string; diff --git a/packages/colors/package.json b/packages/colors/package.json index 6bf6d0660..3eefce7b6 100644 --- a/packages/colors/package.json +++ b/packages/colors/package.json @@ -21,7 +21,11 @@ "dev": "bun run build -- --watch ./src", "publish-to-npm": "../../scripts/publish-if-new.sh" }, - "files": ["dist", "README.md", "CHANGELOG.md"], + "files": [ + "dist", + "README.md", + "CHANGELOG.md" + ], "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" diff --git a/packages/embed/package.json b/packages/embed/package.json index 83a6ba0c6..720ad6474 100644 --- a/packages/embed/package.json +++ b/packages/embed/package.json @@ -39,7 +39,11 @@ "dev": "bun run build -- --watch ./src", "publish-to-npm": "../../scripts/publish-if-new.sh" }, - "files": ["dist", "README.md", "CHANGELOG.md"], + "files": [ + "dist", + "README.md", + "CHANGELOG.md" + ], "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" diff --git a/packages/embed/src/client/createGitBookFrame.ts b/packages/embed/src/client/createGitBookFrame.ts index 579bb5725..b702229c0 100644 --- a/packages/embed/src/client/createGitBookFrame.ts +++ b/packages/embed/src/client/createGitBookFrame.ts @@ -1,4 +1,5 @@ import { createChannel } from 'bidc'; + import type { FrameToParentMessage, GitBookEmbeddableConfiguration, @@ -76,7 +77,7 @@ export function createGitBookFrame(iframe: HTMLIFrameElement): GitBookFrameClien channel.send(message); }; - const events = new Map void>>(); + const events = new Map void)[]>(); const configuration: GitBookEmbeddableConfiguration = { tabs: ['assistant', 'search', 'docs'], diff --git a/packages/embed/src/react/GitBookFrame.tsx b/packages/embed/src/react/GitBookFrame.tsx index e0fe7dcc0..e23e31c0b 100644 --- a/packages/embed/src/react/GitBookFrame.tsx +++ b/packages/embed/src/react/GitBookFrame.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect, useMemo, useRef, useState } from 'react'; + import type { GetFrameURLOptions, GitBookEmbeddableConfiguration, diff --git a/packages/embed/src/react/GitBookProvider.tsx b/packages/embed/src/react/GitBookProvider.tsx index f7ab7b94f..95bcfdb77 100644 --- a/packages/embed/src/react/GitBookProvider.tsx +++ b/packages/embed/src/react/GitBookProvider.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; + import { type CreateGitBookOptions, createGitBook } from '../client'; import { GitBookContext } from './context'; diff --git a/packages/embed/src/react/context.ts b/packages/embed/src/react/context.ts index b0c872c59..b6b3606a0 100644 --- a/packages/embed/src/react/context.ts +++ b/packages/embed/src/react/context.ts @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; + import type { GitBookClient } from '../client'; export const GitBookContext = React.createContext(null); diff --git a/packages/emoji-codepoints/build.ts b/packages/emoji-codepoints/build.ts index 4883ecb51..67261868b 100644 --- a/packages/emoji-codepoints/build.ts +++ b/packages/emoji-codepoints/build.ts @@ -1,6 +1,6 @@ +import emojisRaws from 'emoji-assets/emoji.json'; import fs from 'node:fs'; import path from 'node:path'; -import emojisRaws from 'emoji-assets/emoji.json'; interface EmojiData { code_points: { diff --git a/packages/expr/package.json b/packages/expr/package.json index 7b4a0e8d1..06363f51c 100644 --- a/packages/expr/package.json +++ b/packages/expr/package.json @@ -36,7 +36,11 @@ "dev": "bun run build -- --watch ./src", "publish-to-npm": "../../scripts/publish-if-new.sh" }, - "files": ["dist", "README.md", "CHANGELOG.md"], + "files": [ + "dist", + "README.md", + "CHANGELOG.md" + ], "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" diff --git a/packages/expr/src/__tests__/autocomplete.test.ts b/packages/expr/src/__tests__/autocomplete.test.ts index 64c7647ec..31632bba6 100644 --- a/packages/expr/src/__tests__/autocomplete.test.ts +++ b/packages/expr/src/__tests__/autocomplete.test.ts @@ -58,10 +58,10 @@ describe('autocomplete', () => { }), }; const context = new SymbolsTable(symbols); - const SCENARIOS: Array<{ + const SCENARIOS: { expressionWithCursor: string; expectedSuggestions: AutocompleteSuggestions; - }> = [ + }[] = [ { expressionWithCursor: 'visit', expectedSuggestions: [ diff --git a/packages/expr/src/autocomplete.ts b/packages/expr/src/autocomplete.ts index e87791f1b..4c36c62ea 100644 --- a/packages/expr/src/autocomplete.ts +++ b/packages/expr/src/autocomplete.ts @@ -11,7 +11,6 @@ import type { } from 'acorn'; import { isDummy } from 'acorn-loose'; import * as walk from 'acorn-walk'; - import assertNever from 'assert-never'; import { type ExtractSymbolDef, SymbolType, SymbolsTable } from './symbols'; @@ -312,7 +311,7 @@ export class AutoComplete { node: BinaryExpression, cursorOffset: number, context: SymbolsTable - ): Array { + ): AutocompleteLiteralValueSuggestion[] { const { left, right } = node; if (left.type !== 'MemberExpression' && left.type !== 'Identifier') { @@ -431,7 +430,7 @@ export class AutoComplete { node: AnyNode, cursorOffset: number, context: SymbolsTable - ): Array { + ): AutocompleteOperatorSuggestion[] { if (node.type === 'Literal') { const parent = findParentNode(node, ast); @@ -496,7 +495,7 @@ export class AutoComplete { */ private getOperatorSuggestionsForSymbol( symbol: ExtractSymbolDef - ): Array { + ): AutocompleteOperatorSuggestion[] { switch (symbol.type) { case SymbolType.Number: case SymbolType.Boolean: @@ -526,7 +525,7 @@ export class AutoComplete { case SymbolType.Function: return this.getOperatorSuggestionsForSymbol(symbol.returns); case SymbolType.Union: { - return symbol.members.reduce>((prev, cur) => { + return symbol.members.reduce((prev, cur) => { prev.push(...this.getOperatorSuggestionsForSymbol(cur)); return prev; }, []); diff --git a/packages/expr/src/input-values.ts b/packages/expr/src/input-values.ts index 6aa75cf46..b901d5b07 100644 --- a/packages/expr/src/input-values.ts +++ b/packages/expr/src/input-values.ts @@ -1,4 +1,5 @@ import type { JSONSchema7 } from 'json-schema'; + import { filterOutNullable } from './utils'; type InputValuesType = diff --git a/packages/expr/src/types.ts b/packages/expr/src/types.ts index 7d721480a..0840b0449 100644 --- a/packages/expr/src/types.ts +++ b/packages/expr/src/types.ts @@ -20,7 +20,7 @@ export interface ExpressionParserResult { /** * The information of the invalid (non-expression) nodes found from the other portions of the parsed expression. */ - invalidNodes: Array; + invalidNodes: ExpressionStatement[]; } export interface ExpressionAutocompleteResults { @@ -158,11 +158,11 @@ export interface AutocompleteSymbolSuggestion { symbol: SymbolInfo; } -export type AutocompleteSuggestions = Array< +export type AutocompleteSuggestions = ( | AutocompleteSymbolSuggestion | AutocompleteLiteralValueSuggestion | AutocompleteOperatorSuggestion ->; +)[]; type LoggerFn = (message: string, ...args: any[]) => void; diff --git a/packages/fonts/bin/generate.ts b/packages/fonts/bin/generate.ts index d4326d615..af2cb2861 100644 --- a/packages/fonts/bin/generate.ts +++ b/packages/fonts/bin/generate.ts @@ -1,8 +1,7 @@ +import { APIv2 } from 'google-font-metadata'; import fs from 'node:fs/promises'; import path from 'node:path'; -import { APIv2 } from 'google-font-metadata'; - import { CustomizationDefaultFont } from '@gitbook/api'; import type { FontDefinitions } from '../src/types'; diff --git a/packages/fonts/package.json b/packages/fonts/package.json index 935019b01..8e5277325 100644 --- a/packages/fonts/package.json +++ b/packages/fonts/package.json @@ -27,7 +27,12 @@ "unit": "bun test", "publish-to-npm": "../../scripts/publish-if-new.sh" }, - "files": ["dist", "bin", "README.md", "CHANGELOG.md"], + "files": [ + "dist", + "bin", + "README.md", + "CHANGELOG.md" + ], "engines": { "node": ">=20.0.0" }, diff --git a/packages/fonts/src/fonts.ts b/packages/fonts/src/fonts.ts index c9544efb1..f176276c0 100644 --- a/packages/fonts/src/fonts.ts +++ b/packages/fonts/src/fonts.ts @@ -1,5 +1,4 @@ +import rawFonts from './data/fonts.json' with { type: 'json' }; import type { FontDefinitions } from './types'; -import rawFonts from './data/fonts.json' with { type: 'json' }; - export const fonts: FontDefinitions = rawFonts; diff --git a/packages/fonts/src/getDefaultFont.test.ts b/packages/fonts/src/getDefaultFont.test.ts index 58afbeac9..8efa3196d 100644 --- a/packages/fonts/src/getDefaultFont.test.ts +++ b/packages/fonts/src/getDefaultFont.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from 'bun:test'; + import { CustomizationDefaultFont } from '@gitbook/api'; + import { getDefaultFont } from './getDefaultFont'; describe('getDefaultFont', () => { diff --git a/packages/fonts/src/getDefaultFont.ts b/packages/fonts/src/getDefaultFont.ts index d5ffa48df..5fdabc837 100644 --- a/packages/fonts/src/getDefaultFont.ts +++ b/packages/fonts/src/getDefaultFont.ts @@ -1,4 +1,5 @@ import type { CustomizationDefaultFont } from '@gitbook/api'; + import { fonts } from './fonts'; import type { FontWeight } from './types'; @@ -86,7 +87,7 @@ function getBestUnicodeRange(text: string, ranges: Record): stri // ---------- tally code-point hits ---------- const hits: Record = Object.fromEntries(Object.keys(parsed).map((k) => [k, 0])); - for (let i = 0; i < text.length; ) { + for (let i = 0; i < text.length;) { const cp = text.codePointAt(i)!; i += cp > 0xffff ? 2 : 1; // advance by 1 UTF-16 code-unit (or 2 for surrogates) diff --git a/packages/gitbook/e2e/internal.spec.ts b/packages/gitbook/e2e/internal.spec.ts index 77ee57d9d..4fa7986ff 100644 --- a/packages/gitbook/e2e/internal.spec.ts +++ b/packages/gitbook/e2e/internal.spec.ts @@ -1,3 +1,6 @@ +import { expect } from '@playwright/test'; +import jwt from 'jsonwebtoken'; + import { CustomizationAIMode, CustomizationBackground, @@ -11,10 +14,6 @@ import { SiteSocialAccountPlatform, } from '@gitbook/api'; import type { GitBookStandalone } from '@gitbook/embed'; -import { expect } from '@playwright/test'; -import jwt from 'jsonwebtoken'; - -import { VISITOR_TOKEN_COOKIE } from '@/lib/visitors'; import { getGitBookPreviewURL, getSiteAPIToken } from '../tests/utils'; import { @@ -37,6 +36,7 @@ import { waitForCoverImages, waitForNotFound, } from './util'; +import { VISITOR_TOKEN_COOKIE } from '@/lib/visitors'; // Kept as deterministic as possible to reduce visual flakiness: no preamble, a // single fixed search, a concise answer, and a fixed number of follow-ups. The @@ -1241,8 +1241,8 @@ const testCases: TestsCase[] = [ }, // New site themes ...allThemes.flatMap((theme) => [ - ...allTintColors.flatMap((tint) => [ - ...allSidebarBackgroundStyles.flatMap((sidebarStyle) => ({ + ...allTintColors.flatMap((tint) => + allSidebarBackgroundStyles.flatMap((sidebarStyle) => ({ name: `Theme ${theme} - Tint ${tint.label} - Sidebar ${sidebarStyle} - Mode ${themeMode}`, url: getCustomizationURL({ styling: { @@ -1262,8 +1262,8 @@ const testCases: TestsCase[] = [ }, }), run: waitForCookiesDialog, - })), - ]), + })) + ), ...allSearchStyles.flatMap((searchStyle) => ({ name: `Theme ${theme} – Search ${searchStyle} – Mode ${themeMode}`, url: getCustomizationURL({ @@ -1283,8 +1283,8 @@ const testCases: TestsCase[] = [ })), ]), // Deprecated header themes - ...allDeprecatedThemePresets.flatMap((preset) => [ - ...allSidebarBackgroundStyles.flatMap((sidebarStyle) => ({ + ...allDeprecatedThemePresets.flatMap((preset) => + allSidebarBackgroundStyles.flatMap((sidebarStyle) => ({ name: `With tint - Legacy header preset ${preset} - Sidebar ${sidebarStyle} - Theme mode ${themeMode}`, url: getCustomizationURL({ styling: { @@ -1310,8 +1310,8 @@ const testCases: TestsCase[] = [ }, }), run: waitForCookiesDialog, - })), - ]), + })) + ), { name: `With tint - Legacy background match - Theme mode ${themeMode}`, url: getCustomizationURL({ diff --git a/packages/gitbook/e2e/pdf.spec.ts b/packages/gitbook/e2e/pdf.spec.ts index b694689c9..b8ecc03b2 100644 --- a/packages/gitbook/e2e/pdf.spec.ts +++ b/packages/gitbook/e2e/pdf.spec.ts @@ -1,5 +1,6 @@ import { argosScreenshot } from '@argos-ci/playwright'; import { expect, test } from '@playwright/test'; + import { getContentTestURL } from '../tests/utils'; import { waitForIcons } from './util'; diff --git a/packages/gitbook/e2e/util.ts b/packages/gitbook/e2e/util.ts index 402f4aede..41a8da8ad 100644 --- a/packages/gitbook/e2e/util.ts +++ b/packages/gitbook/e2e/util.ts @@ -1,4 +1,16 @@ import { argosScreenshot } from '@argos-ci/playwright'; +import { + type BrowserContext, + type FrameLocator, + type Page, + type Response, + expect, + test, +} from '@playwright/test'; +import deepMerge from 'deepmerge'; +import rison from 'rison'; +import type { DeepPartial } from 'ts-essentials'; + import { CustomizationAIMode, CustomizationBackground, @@ -22,17 +34,6 @@ import { type SiteCustomizationSettings, SiteExternalLinksTarget, } from '@gitbook/api'; -import { - type BrowserContext, - type FrameLocator, - type Page, - type Response, - expect, - test, -} from '@playwright/test'; -import deepMerge from 'deepmerge'; -import rison from 'rison'; -import type { DeepPartial } from 'ts-essentials'; import { getContentTestURL, getTestURL } from '../tests/utils'; @@ -91,7 +92,7 @@ export interface Test { export type TestsCase = { name: string; skip?: boolean; - tests: Array; + tests: Test[]; contentBaseURL?: string; /** * Whether screenshots in this test case should capture the full scrollable page by default. @@ -111,10 +112,10 @@ export const allThemeModes: CustomizationDefaultThemeMode[] = [ CustomizationDefaultThemeMode.Dark, ]; -export const allTintColors: Array<{ +export const allTintColors: { label: string; value: CustomizationThemedColor | undefined; -}> = [ +}[] = [ { label: 'Off', value: undefined, diff --git a/packages/gitbook/openNext/customWorkers/default.js b/packages/gitbook/openNext/customWorkers/default.js index c7da9ee96..ce48cd4b7 100644 --- a/packages/gitbook/openNext/customWorkers/default.js +++ b/packages/gitbook/openNext/customWorkers/default.js @@ -1,7 +1,7 @@ -import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js'; - import { DurableObject } from 'cloudflare:workers'; +import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js'; + //Only needed to run locally, in prod we'll use the one from do.js export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js'; @@ -28,9 +28,8 @@ export default { async fetch(request, env, ctx) { return runWithCloudflareRequestContext(request, env, ctx, async () => { // We can't move the handler import to the top level, otherwise the runtime will not be properly initialized - const { handler } = await import( - '../../.open-next/server-functions/default/handler.mjs' - ); + const { handler } = + await import('../../.open-next/server-functions/default/handler.mjs'); // - `Request`s are handled by the Next server return handler(request, env, ctx); diff --git a/packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc b/packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc index 975361d64..9d98c9150 100644 --- a/packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc +++ b/packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc @@ -7,174 +7,174 @@ "compatibility_flags": [ "nodejs_compat", "allow_importable_env", - "global_fetch_strictly_public" + "global_fetch_strictly_public", ], "observability": { - "enabled": false + "enabled": false, }, "vars": { - "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true" + "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true", }, "env": { "dev": { "vars": { "STAGE": "dev", "OPEN_NEXT_REQUEST_ID_HEADER": "true", - "GITBOOK_URL": "http://localhost:8771" + "GITBOOK_URL": "http://localhost:8771", }, "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-preview" - } + "bucket_name": "gitbook-open-v2-cache-preview", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-server-dev" - } + "service": "gitbook-open-v2-server-dev", + }, ], "durable_objects": { "bindings": [ { "name": "WRITE_BUFFER", - "class_name": "R2WriteBuffer" + "class_name": "R2WriteBuffer", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", - "class_name": "DOShardedTagCache" - } - ] + "class_name": "DOShardedTagCache", + }, + ], }, "migrations": [ { "tag": "v1", - "new_sqlite_classes": ["R2WriteBuffer", "DOShardedTagCache"] - } - ] + "new_sqlite_classes": ["R2WriteBuffer", "DOShardedTagCache"], + }, + ], }, "preview": { "vars": { "STAGE": "preview", // Just as a test for the preview environment to check that everything works - "NEXT_PRIVATE_DEBUG_CACHE": "true" + "NEXT_PRIVATE_DEBUG_CACHE": "true", }, "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-preview" - } + "bucket_name": "gitbook-open-v2-cache-preview", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-server-preview" - } + "service": "gitbook-open-v2-server-preview", + }, ], "durable_objects": { "bindings": [ { "name": "WRITE_BUFFER", "class_name": "R2WriteBuffer", - "script_name": "gitbook-open-v2-do-preview" + "script_name": "gitbook-open-v2-do-preview", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", - "script_name": "gitbook-open-v2-do-preview" + "script_name": "gitbook-open-v2-do-preview", }, { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler", - "script_name": "gitbook-open-v2-do-preview" - } - ] - } + "script_name": "gitbook-open-v2-do-preview", + }, + ], + }, }, "staging": { "vars": { - "OPEN_NEXT_REQUEST_ID_HEADER": "true" + "OPEN_NEXT_REQUEST_ID_HEADER": "true", }, "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-staging" - } + "bucket_name": "gitbook-open-v2-cache-staging", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-server-staging" - } + "service": "gitbook-open-v2-server-staging", + }, ], "durable_objects": { "bindings": [ { "name": "WRITE_BUFFER", "class_name": "R2WriteBuffer", - "script_name": "gitbook-open-v2-do-staging" + "script_name": "gitbook-open-v2-do-staging", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", - "script_name": "gitbook-open-v2-do-staging" + "script_name": "gitbook-open-v2-do-staging", }, { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler", - "script_name": "gitbook-open-v2-do-staging" - } - ] + "script_name": "gitbook-open-v2-do-staging", + }, + ], }, "tail_consumers": [ { - "service": "gitbook-x-staging-tail" - } - ] + "service": "gitbook-x-staging-tail", + }, + ], }, "production": { "vars": { // This is a bit misleading, but it means that we can have 500 concurrent revalidations // This means that we'll have up to 100 durable objects instance running at the same time "MAX_REVALIDATE_CONCURRENCY": "100", - "OPEN_NEXT_REQUEST_ID_HEADER": "true" + "OPEN_NEXT_REQUEST_ID_HEADER": "true", }, "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-production" - } + "bucket_name": "gitbook-open-v2-cache-production", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-server-production" - } + "service": "gitbook-open-v2-server-production", + }, ], "durable_objects": { "bindings": [ { "name": "WRITE_BUFFER", "class_name": "R2WriteBuffer", - "script_name": "gitbook-open-v2-do-production" + "script_name": "gitbook-open-v2-do-production", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", - "script_name": "gitbook-open-v2-do-production" + "script_name": "gitbook-open-v2-do-production", }, { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler", - "script_name": "gitbook-open-v2-do-production" - } - ] + "script_name": "gitbook-open-v2-do-production", + }, + ], }, "tail_consumers": [ { - "service": "gitbook-x-prod-tail" - } - ] - } - } + "service": "gitbook-x-prod-tail", + }, + ], + }, + }, } diff --git a/packages/gitbook/openNext/customWorkers/doWrangler.jsonc b/packages/gitbook/openNext/customWorkers/doWrangler.jsonc index 54c0ed644..48a72a3cb 100644 --- a/packages/gitbook/openNext/customWorkers/doWrangler.jsonc +++ b/packages/gitbook/openNext/customWorkers/doWrangler.jsonc @@ -6,96 +6,96 @@ "compatibility_flags": [ "nodejs_compat", "allow_importable_env", - "global_fetch_strictly_public" + "global_fetch_strictly_public", ], "observability": { - "enabled": false + "enabled": false, }, "env": { "preview": { "vars": { "STAGE": "preview", - "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true" + "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true", }, "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-preview" - } + "bucket_name": "gitbook-open-v2-cache-preview", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-preview" - } + "service": "gitbook-open-v2-preview", + }, ], "durable_objects": { "bindings": [ { "name": "NEXT_CACHE_DO_QUEUE", - "class_name": "DOQueueHandler" + "class_name": "DOQueueHandler", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", - "class_name": "DOShardedTagCache" + "class_name": "DOShardedTagCache", }, { "name": "WRITE_BUFFER", - "class_name": "R2WriteBuffer" - } - ] + "class_name": "R2WriteBuffer", + }, + ], }, "migrations": [ { "tag": "v1", - "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"] - } - ] + "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"], + }, + ], }, "staging": { "vars": { "STAGE": "staging", - "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true" + "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true", }, "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-staging" - } + "bucket_name": "gitbook-open-v2-cache-staging", + }, ], "tail_consumers": [ { - "service": "gitbook-x-staging-tail" - } + "service": "gitbook-x-staging-tail", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-staging" - } + "service": "gitbook-open-v2-staging", + }, ], "durable_objects": { "bindings": [ { "name": "NEXT_CACHE_DO_QUEUE", - "class_name": "DOQueueHandler" + "class_name": "DOQueueHandler", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", - "class_name": "DOShardedTagCache" + "class_name": "DOShardedTagCache", }, { "name": "WRITE_BUFFER", - "class_name": "R2WriteBuffer" - } - ] + "class_name": "R2WriteBuffer", + }, + ], }, "migrations": [ { "tag": "v1", - "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"] - } - ] + "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"], + }, + ], }, "production": { "vars": { @@ -104,47 +104,47 @@ // We don't want to pollute the memory with broken cache entries // Most of the time, those are fake requests. "NEXT_CACHE_DO_QUEUE_MAX_RETRIES": "1", - "STAGE": "production" + "STAGE": "production", }, "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-production" - } + "bucket_name": "gitbook-open-v2-cache-production", + }, ], "tail_consumers": [ { - "service": "gitbook-x-prod-tail" - } + "service": "gitbook-x-prod-tail", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-production" - } + "service": "gitbook-open-v2-production", + }, ], "durable_objects": { "bindings": [ { "name": "NEXT_CACHE_DO_QUEUE", - "class_name": "DOQueueHandler" + "class_name": "DOQueueHandler", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", - "class_name": "DOShardedTagCache" + "class_name": "DOShardedTagCache", }, { "name": "WRITE_BUFFER", - "class_name": "R2WriteBuffer" - } - ] + "class_name": "R2WriteBuffer", + }, + ], }, "migrations": [ { "tag": "v1", - "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"] - } - ] - } - } + "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"], + }, + ], + }, + }, } diff --git a/packages/gitbook/openNext/customWorkers/middleware.js b/packages/gitbook/openNext/customWorkers/middleware.js index e6fc2f751..0a519d278 100644 --- a/packages/gitbook/openNext/customWorkers/middleware.js +++ b/packages/gitbook/openNext/customWorkers/middleware.js @@ -1,6 +1,6 @@ import { WorkerEntrypoint } from 'cloudflare:workers'; -import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js'; +import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js'; import { handler as middlewareHandler } from '../../.open-next/middleware/handler.mjs'; export { DOQueueHandler } from '../../.open-next/.build/durable-objects/queue.js'; @@ -38,7 +38,7 @@ export default class extends WorkerEntrypoint { reqOrResp.headers.get('x-opennext-cache') ?? 'MISS', getResolvedRoute(reqOrResp, 'middleware') ); - // biome-ignore lint/suspicious/noConsole: + // oxlint-disable-next-line no-console console.log(logMessage); return reqOrResp; } @@ -61,7 +61,7 @@ export default class extends WorkerEntrypoint { `SERVER-${response.headers.get('x-nextjs-cache') ?? 'MISS'}`, getResolvedRoute(reqOrResp, 'unresolved') ); - // biome-ignore lint/suspicious/noConsole: + // oxlint-disable-next-line no-console console.log(formatedLog); return response; diff --git a/packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc index 563aa689b..9e08e264d 100644 --- a/packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc +++ b/packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc @@ -6,17 +6,17 @@ "compatibility_flags": [ "nodejs_compat", "allow_importable_env", - "global_fetch_strictly_public" + "global_fetch_strictly_public", ], "observability": { - "enabled": false + "enabled": false, }, "assets": { "directory": "../../.open-next/assets", - "binding": "ASSETS" + "binding": "ASSETS", }, "vars": { - "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true" + "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true", }, "env": { "dev": { @@ -27,129 +27,129 @@ // we should just bypass the cache to go to the server directly "SHOULD_BYPASS_CACHE": "true", "OPEN_NEXT_REQUEST_ID_HEADER": "true", - "GITBOOK_URL": "http://localhost:8771" + "GITBOOK_URL": "http://localhost:8771", }, "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-preview" - } + "bucket_name": "gitbook-open-v2-cache-preview", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-dev" + "service": "gitbook-open-v2-dev", }, { "binding": "DEFAULT_WORKER", - "service": "gitbook-open-v2-server-dev" - } - ] + "service": "gitbook-open-v2-server-dev", + }, + ], }, "preview": { "vars": { "STAGE": "preview", "PREVIEW_HOSTNAME": "TO_REPLACE", - "WORKER_VERSION_ID": "TO_REPLACE" + "WORKER_VERSION_ID": "TO_REPLACE", }, "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-preview" - } + "bucket_name": "gitbook-open-v2-cache-preview", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-preview" + "service": "gitbook-open-v2-preview", }, { "binding": "DEFAULT_WORKER", - "service": "gitbook-open-v2-server-preview" - } + "service": "gitbook-open-v2-server-preview", + }, ], "durable_objects": { "bindings": [ { "name": "WRITE_BUFFER", "class_name": "R2WriteBuffer", - "script_name": "gitbook-open-v2-do-preview" + "script_name": "gitbook-open-v2-do-preview", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", - "script_name": "gitbook-open-v2-do-preview" + "script_name": "gitbook-open-v2-do-preview", }, { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler", - "script_name": "gitbook-open-v2-do-preview" - } - ] - } + "script_name": "gitbook-open-v2-do-preview", + }, + ], + }, }, "staging": { "vars": { "STAGE": "staging", "WORKER_VERSION_ID": "TO_REPLACE", - "OPEN_NEXT_REQUEST_ID_HEADER": "true" + "OPEN_NEXT_REQUEST_ID_HEADER": "true", }, "routes": [ { "pattern": "open-2c.gitbook-staging.com/*", - "zone_name": "gitbook-staging.com" + "zone_name": "gitbook-staging.com", }, { "pattern": "static-2c.gitbook-staging.com/*", - "zone_name": "gitbook-staging.com" - } + "zone_name": "gitbook-staging.com", + }, ], "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-staging" - } + "bucket_name": "gitbook-open-v2-cache-staging", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-staging" + "service": "gitbook-open-v2-staging", }, { "binding": "DEFAULT_WORKER", - "service": "gitbook-open-v2-server-staging" - } + "service": "gitbook-open-v2-server-staging", + }, ], "tail_consumers": [ { - "service": "gitbook-x-staging-tail" - } + "service": "gitbook-x-staging-tail", + }, ], "durable_objects": { "bindings": [ { "name": "WRITE_BUFFER", "class_name": "R2WriteBuffer", - "script_name": "gitbook-open-v2-do-staging" + "script_name": "gitbook-open-v2-do-staging", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", - "script_name": "gitbook-open-v2-do-staging" + "script_name": "gitbook-open-v2-do-staging", }, { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler", - "script_name": "gitbook-open-v2-do-staging" - } - ] + "script_name": "gitbook-open-v2-do-staging", + }, + ], }, "migrations": [ { "tag": "v1", - "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"] - } - ] + "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"], + }, + ], }, "production": { "vars": { @@ -161,64 +161,64 @@ "DEBUG_CLOUDFLARE": "true", "WORKER_VERSION_ID": "TO_REPLACE", "STAGE": "production", - "OPEN_NEXT_REQUEST_ID_HEADER": "true" + "OPEN_NEXT_REQUEST_ID_HEADER": "true", }, "routes": [ { "pattern": "open-2c.gitbook.com/*", - "zone_name": "gitbook.com" + "zone_name": "gitbook.com", }, { "pattern": "static-2c.gitbook.com/*", - "zone_name": "gitbook.com" - } + "zone_name": "gitbook.com", + }, ], "r2_buckets": [ { "binding": "NEXT_INC_CACHE_R2_BUCKET", - "bucket_name": "gitbook-open-v2-cache-production" - } + "bucket_name": "gitbook-open-v2-cache-production", + }, ], "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "gitbook-open-v2-production" + "service": "gitbook-open-v2-production", }, { "binding": "DEFAULT_WORKER", - "service": "gitbook-open-v2-server-production" - } + "service": "gitbook-open-v2-server-production", + }, ], "tail_consumers": [ { - "service": "gitbook-x-prod-tail" - } + "service": "gitbook-x-prod-tail", + }, ], "durable_objects": { "bindings": [ { "name": "WRITE_BUFFER", "class_name": "R2WriteBuffer", - "script_name": "gitbook-open-v2-do-production" + "script_name": "gitbook-open-v2-do-production", }, { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", - "script_name": "gitbook-open-v2-do-production" + "script_name": "gitbook-open-v2-do-production", }, { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler", - "script_name": "gitbook-open-v2-do-production" - } - ] + "script_name": "gitbook-open-v2-do-production", + }, + ], }, "migrations": [ { "tag": "v1", - "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"] - } - ] - } - } + "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"], + }, + ], + }, + }, } diff --git a/packages/gitbook/openNext/incrementalCache/incrementalCache.ts b/packages/gitbook/openNext/incrementalCache/incrementalCache.ts index 11c0f8977..550c355ea 100644 --- a/packages/gitbook/openNext/incrementalCache/incrementalCache.ts +++ b/packages/gitbook/openNext/incrementalCache/incrementalCache.ts @@ -1,4 +1,4 @@ -import { createHash } from 'node:crypto'; +import type { DurableObjectNamespace, Rpc } from '@cloudflare/workers-types'; import type { CacheEntryType, CacheValue, @@ -6,8 +6,7 @@ import type { WithLastModified, } from '@opennextjs/aws/types/overrides.js'; import { getCloudflareContext } from '@opennextjs/cloudflare'; - -import type { DurableObjectNamespace, Rpc } from '@cloudflare/workers-types'; +import { createHash } from 'node:crypto'; export const BINDING_NAME = 'NEXT_INC_CACHE_R2_BUCKET'; export const DEFAULT_PREFIX = 'incremental-cache'; diff --git a/packages/gitbook/openNext/incrementalCache/middleware.ts b/packages/gitbook/openNext/incrementalCache/middleware.ts index 0f37e6563..2d116ec2b 100644 --- a/packages/gitbook/openNext/incrementalCache/middleware.ts +++ b/packages/gitbook/openNext/incrementalCache/middleware.ts @@ -1,4 +1,5 @@ import { withRegionalCache } from '@opennextjs/cloudflare/overrides/incremental-cache/regional-cache'; + import { GitbookIncrementalCache } from './incrementalCache'; export default withRegionalCache(new GitbookIncrementalCache(), { diff --git a/packages/gitbook/openNext/incrementalCache/server.ts b/packages/gitbook/openNext/incrementalCache/server.ts index 3b32d7580..359920de7 100644 --- a/packages/gitbook/openNext/incrementalCache/server.ts +++ b/packages/gitbook/openNext/incrementalCache/server.ts @@ -1,4 +1,5 @@ import { withRegionalCache } from '@opennextjs/cloudflare/overrides/incremental-cache/regional-cache'; + import { GitbookIncrementalCache } from './incrementalCache'; // We cannot have regional cache only in the middleware, otherwise it will override things on cache miss diff --git a/packages/gitbook/openNext/queue/server.ts b/packages/gitbook/openNext/queue/server.ts index 9e3756748..a0c6fd3da 100644 --- a/packages/gitbook/openNext/queue/server.ts +++ b/packages/gitbook/openNext/queue/server.ts @@ -1,6 +1,7 @@ -import { getLogger } from '@/lib/logger'; import type { Queue } from '@opennextjs/aws/types/overrides.js'; +import { getLogger } from '@/lib/logger'; + export default { name: 'GitbookISRQueue', send: async (msg) => { diff --git a/packages/gitbook/openNext/tagCache/middleware.ts b/packages/gitbook/openNext/tagCache/middleware.ts index eca3d0cde..6b26d0d1e 100644 --- a/packages/gitbook/openNext/tagCache/middleware.ts +++ b/packages/gitbook/openNext/tagCache/middleware.ts @@ -1,8 +1,9 @@ -import { createLogger, getLogger } from '@/lib/logger'; import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js'; import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache'; import { softTagFilter } from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter'; +import { createLogger, getLogger } from '@/lib/logger'; + const originalTagCache = doShardedTagCache({ baseShardSize: 12, regionalCache: true, diff --git a/packages/gitbook/scripts/check-css-browser-compatibility.ts b/packages/gitbook/scripts/check-css-browser-compatibility.ts index 2714f8034..3f0e4df59 100644 --- a/packages/gitbook/scripts/check-css-browser-compatibility.ts +++ b/packages/gitbook/scripts/check-css-browser-compatibility.ts @@ -1,4 +1,5 @@ import { readFile } from 'node:fs/promises'; + import { type CompatibilityDiagnostic, type IssueCommentClient, diff --git a/packages/gitbook/scripts/generate-mermaid-runtime.ts b/packages/gitbook/scripts/generate-mermaid-runtime.ts index 3c452b969..f7dd914cb 100644 --- a/packages/gitbook/scripts/generate-mermaid-runtime.ts +++ b/packages/gitbook/scripts/generate-mermaid-runtime.ts @@ -1,8 +1,8 @@ +import { build } from 'bun'; import { mkdir, rename, rm } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { build } from 'bun'; import { MERMAID_RUNTIME_PATH } from '../src/components/DocumentView/CodeBlock/mermaid-runtime-path'; diff --git a/packages/gitbook/scripts/generate-scalar-runtime.ts b/packages/gitbook/scripts/generate-scalar-runtime.ts index 6495469f5..ab2eee91b 100644 --- a/packages/gitbook/scripts/generate-scalar-runtime.ts +++ b/packages/gitbook/scripts/generate-scalar-runtime.ts @@ -1,8 +1,8 @@ +import { build } from 'bun'; import { mkdir, rename, rm } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { build } from 'bun'; import { SCALAR_RUNTIME_PATH } from '../src/components/DocumentView/OpenAPI/scalar-runtime-path'; diff --git a/packages/gitbook/scripts/profile-opennext-memory.ts b/packages/gitbook/scripts/profile-opennext-memory.ts index 9cc538e6e..77ef84a86 100644 --- a/packages/gitbook/scripts/profile-opennext-memory.ts +++ b/packages/gitbook/scripts/profile-opennext-memory.ts @@ -1,7 +1,7 @@ +import { file, sleep, spawn } from 'bun'; import { stat } from 'node:fs/promises'; import { createConnection } from 'node:net'; import { join } from 'node:path'; -import { file, sleep, spawn } from 'bun'; import WebSocket from 'ws'; type HeapUsage = { @@ -60,7 +60,7 @@ try { const afterLoad = await getMeasurements(); const bundle = await getBundleMetrics(); - // biome-ignore lint/suspicious/noConsole: JSON on stdout is this script's public interface. + // oxlint-disable-next-line no-console -- JSON on stdout is this script's public interface. console.log( JSON.stringify( { diff --git a/packages/gitbook/src/app/global-error.tsx b/packages/gitbook/src/app/global-error.tsx index abf0186a4..5242e8c8c 100644 --- a/packages/gitbook/src/app/global-error.tsx +++ b/packages/gitbook/src/app/global-error.tsx @@ -2,11 +2,7 @@ import NextError from 'next/error'; -export default function GlobalError({ - error, -}: { - error: Error & { digest?: string }; -}) { +export default function GlobalError({ error }: { error: Error & { digest?: string } }) { console.error('Global error:', error); return ( diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/page.tsx b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/page.tsx index 8f426d7f4..3e30c573e 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/page.tsx +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/page.tsx @@ -1,10 +1,11 @@ +import type { Metadata, Viewport } from 'next'; + import { type RouteParams, getDynamicSiteContext, getPagePathFromParams } from '@/app/utils'; import { SitePage, generateSitePageMetadata, generateSitePageViewport, } from '@/components/SitePage'; -import type { Metadata, Viewport } from 'next'; type PageProps = { params: Promise; diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/layout.tsx b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/layout.tsx index aa267b964..7129cfba1 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/layout.tsx +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/(content)/layout.tsx @@ -1,3 +1,5 @@ +import { headers } from 'next/headers'; + import { type RouteLayoutParams, getDynamicSiteContext } from '@/app/utils'; import { CustomizationRootLayout } from '@/components/RootLayout'; import { @@ -7,7 +9,6 @@ import { } from '@/components/SiteLayout'; import { getThemeFromMiddleware } from '@/lib/middleware'; import { shouldTrackEvents } from '@/lib/tracking'; -import { headers } from 'next/headers'; interface SiteDynamicLayoutProps { params: Promise; diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/auth/login/route.ts b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/auth/login/route.ts index 3e2362db8..7f6c80eea 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/auth/login/route.ts +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/auth/login/route.ts @@ -1,7 +1,8 @@ -import { type RouteLayoutParams, getDynamicSiteContext } from '@/app/utils'; import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; +import { type RouteLayoutParams, getDynamicSiteContext } from '@/app/utils'; + /** * Redirect to the upstream auth provider login URL of site, or to the site root when not configured. */ diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/auth/logout/route.ts b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/auth/logout/route.ts index e3714719d..855d7d9b8 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/auth/logout/route.ts +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/auth/logout/route.ts @@ -1,3 +1,7 @@ +import { cookies } from 'next/headers'; +import type { NextRequest } from 'next/server'; +import { NextResponse } from 'next/server'; + import { type RouteLayoutParams, getDynamicSiteContext, @@ -5,9 +9,6 @@ import { } from '@/app/utils'; import { getVisitorAuthBasePath } from '@/lib/data'; import { getVisitorAuthCookieName } from '@/lib/visitors'; -import { cookies } from 'next/headers'; -import type { NextRequest } from 'next/server'; -import { NextResponse } from 'next/server'; /** * Clear the site-scoped auth session cookies and redirect to the site root. diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/assistant/page.tsx b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/assistant/page.tsx index 274f55334..0eead01ec 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/assistant/page.tsx +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/assistant/page.tsx @@ -1,8 +1,9 @@ +import { redirect } from 'next/navigation'; + import type { RouteLayoutParams } from '@/app/utils'; import { EmbeddableAssistantPage } from '@/components/Embeddable'; import { isAIChatEnabled } from '@/components/utils/isAIChatEnabled'; import { getEmbeddableDynamicContext } from '@/lib/embeddable'; -import { redirect } from 'next/navigation'; type PageProps = { params: Promise; diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/layout.tsx b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/layout.tsx index 349844fd5..f9e709952 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/layout.tsx +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/layout.tsx @@ -1,3 +1,5 @@ +import { headers } from 'next/headers'; + import type { RouteLayoutParams } from '@/app/utils'; import { EmbeddableRootLayout, @@ -7,7 +9,6 @@ import { import { getEmbeddableStaticContext } from '@/lib/embeddable'; import { getThemeFromMiddleware } from '@/lib/middleware'; import { shouldTrackEvents } from '@/lib/tracking'; -import { headers } from 'next/headers'; interface SiteStaticLayoutProps { params: Promise; diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/page.tsx b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/page.tsx index 4abe02997..fd11d6d04 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/page.tsx +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/page.tsx @@ -1,7 +1,8 @@ +import { redirect } from 'next/navigation'; + import type { RouteLayoutParams } from '@/app/utils'; import { isAIChatEnabled } from '@/components/utils/isAIChatEnabled'; import { getEmbeddableDynamicContext } from '@/lib/embeddable'; -import { redirect } from 'next/navigation'; type PageProps = { params: Promise; diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/page/[pagePath]/page.tsx b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/page/[pagePath]/page.tsx index 28774e02d..991194cd0 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/page/[pagePath]/page.tsx +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/embed/page/[pagePath]/page.tsx @@ -1,7 +1,8 @@ +import type { Metadata } from 'next'; + import { type RouteParams, getPagePathFromParams } from '@/app/utils'; import { EmbeddableDocsPage, generateEmbeddableDocsPageMetadata } from '@/components/Embeddable'; import { getEmbeddableDynamicContext } from '@/lib/embeddable'; -import type { Metadata } from 'next'; type PageProps = { params: Promise; diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/auth/route.ts b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/auth/route.ts index 3fd3c2ccc..28cc86d35 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/auth/route.ts +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/auth/route.ts @@ -1,6 +1,7 @@ -import type { RouteLayoutParams } from '@/app/utils'; import type { NextRequest } from 'next/server'; + import { handleMcpRequest } from '../handler'; +import type { RouteLayoutParams } from '@/app/utils'; async function handler( rawRequest: NextRequest, diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/handler.ts b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/handler.ts index 9ee489665..7c4ca7ee9 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/handler.ts +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/handler.ts @@ -1,3 +1,7 @@ +import { createMcpHandler } from 'mcp-handler'; +import type { NextRequest } from 'next/server'; +import { z } from 'zod'; + import { CustomizationPageActionType, SiteInsightsDisplayContext } from '@gitbook/api'; import { type RouteLayoutParams, getDynamicSiteContext } from '@/app/utils'; @@ -10,9 +14,6 @@ import { joinPathWithBaseURL } from '@/lib/paths'; import { findSiteSpaceBy, findSiteSpaceByUrl } from '@/lib/sites'; import { trackServerInsightsEvents } from '@/lib/tracking'; import { waitUntil } from '@/lib/waitUntil'; -import { createMcpHandler } from 'mcp-handler'; -import type { NextRequest } from 'next/server'; -import { z } from 'zod'; /** * Fire-and-forget insights tracking for the MCP endpoint. A tracking failure (e.g. a 422 from the diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/route.ts b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/route.ts index adce94298..7a9788ee2 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/route.ts +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/mcp/route.ts @@ -1,6 +1,7 @@ -import type { RouteLayoutParams } from '@/app/utils'; import type { NextRequest } from 'next/server'; + import { handleMcpRequest } from './handler'; +import type { RouteLayoutParams } from '@/app/utils'; async function handler( rawRequest: NextRequest, diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/pdf/page.tsx b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/pdf/page.tsx index 6f1c13e03..bede37932 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/pdf/page.tsx +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/pdf/page.tsx @@ -1,11 +1,7 @@ import { type RouteLayoutParams, getDynamicSiteContext } from '@/app/utils'; import { PDFPage, generatePDFMetadata } from '@/components/PDF'; -export async function generateMetadata({ - params, -}: { - params: Promise; -}) { +export async function generateMetadata({ params }: { params: Promise }) { const { context } = await getDynamicSiteContext(await params); return generatePDFMetadata(context); } diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts index 24607f468..f0658ab8c 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts @@ -1,3 +1,14 @@ +import { type NextRequest, NextResponse } from 'next/server'; + +import type { + SearchPageResult, + SearchSpaceResult, + SiteSection, + SiteSectionGroup, + SiteSpace, +} from '@gitbook/api'; +import type { IconName } from '@gitbook/icons'; + import type { ComputedPageResult, ComputedSectionResult, @@ -10,15 +21,6 @@ import { getSiteURLDataFromMiddleware } from '@/lib/middleware'; import { joinPathWithBaseURL } from '@/lib/paths'; import { getServerActionBaseContext } from '@/lib/server-actions'; import { findSiteSpaceBy, getLocalizedTitle } from '@/lib/sites'; -import type { - SearchPageResult, - SearchSpaceResult, - SiteSection, - SiteSectionGroup, - SiteSpace, -} from '@gitbook/api'; -import type { IconName } from '@gitbook/icons'; -import { type NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { const { asEmbeddable, query, scope } = (await request.json()) as SearchSiteContentRequest; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/page.tsx b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/page.tsx index 111eaadcd..832b73193 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/page.tsx +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/(content)/[pagePath]/page.tsx @@ -1,3 +1,5 @@ +import type { Metadata, Viewport } from 'next'; + import { type RouteParams, getPagePathFromParams, getStaticSiteContext } from '@/app/utils'; import { SitePage, @@ -5,8 +7,6 @@ import { generateSitePageViewport, } from '@/components/SitePage'; -import type { Metadata, Viewport } from 'next'; - export const dynamic = 'force-static'; type PageProps = { diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/assistant/page.tsx b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/assistant/page.tsx index b5e5cfa42..0ca3ce09a 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/assistant/page.tsx +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/assistant/page.tsx @@ -1,8 +1,9 @@ +import { redirect } from 'next/navigation'; + import type { RouteParams } from '@/app/utils'; import { EmbeddableAssistantPage } from '@/components/Embeddable'; import { isAIChatEnabled } from '@/components/utils/isAIChatEnabled'; import { getEmbeddableStaticContext } from '@/lib/embeddable'; -import { redirect } from 'next/navigation'; export const dynamic = 'force-static'; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/demo/route.ts b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/demo/route.ts index 381854f03..43330cc1c 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/demo/route.ts +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/demo/route.ts @@ -1,6 +1,7 @@ -import { type RouteLayoutParams, getStaticSiteContext } from '@/app/utils'; import type { NextRequest } from 'next/server'; +import { type RouteLayoutParams, getStaticSiteContext } from '@/app/utils'; + export const dynamic = 'force-static'; /** diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/page.tsx b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/page.tsx index 0198f23f2..564c441f5 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/page.tsx +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/page.tsx @@ -1,7 +1,8 @@ +import { redirect } from 'next/navigation'; + import type { RouteLayoutParams } from '@/app/utils'; import { isAIChatEnabled } from '@/components/utils/isAIChatEnabled'; import { getEmbeddableStaticContext } from '@/lib/embeddable'; -import { redirect } from 'next/navigation'; export const dynamic = 'force-static'; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/page/[pagePath]/page.tsx b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/page/[pagePath]/page.tsx index 8b66c9163..eb5f30b60 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/page/[pagePath]/page.tsx +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/page/[pagePath]/page.tsx @@ -1,7 +1,8 @@ +import type { Metadata } from 'next'; + import { type RouteParams, getPagePathFromParams } from '@/app/utils'; import { EmbeddableDocsPage, generateEmbeddableDocsPageMetadata } from '@/components/Embeddable'; import { getEmbeddableStaticContext } from '@/lib/embeddable'; -import type { Metadata } from 'next'; export const dynamic = 'force-static'; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/script.js/route.ts b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/script.js/route.ts index 7801d5d17..6e34860f2 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/script.js/route.ts +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/script.js/route.ts @@ -1,9 +1,11 @@ +import type { NextRequest } from 'next/server'; + +import type { CreateGitBookOptions } from '@gitbook/embed'; + import type { RouteLayoutParams } from '@/app/utils'; import { getAssetURL } from '@/lib/assets'; import { buildVersion } from '@/lib/build'; import { getEmbeddableStaticContext } from '@/lib/embeddable'; -import type { CreateGitBookOptions } from '@gitbook/embed'; -import type { NextRequest } from 'next/server'; export const dynamic = 'force-static'; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown-ask/[question]/[goal]/route.ts b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown-ask/[question]/[goal]/route.ts index db90ed917..72ba6ecb0 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown-ask/[question]/[goal]/route.ts +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown-ask/[question]/[goal]/route.ts @@ -1,6 +1,7 @@ +import type { NextRequest } from 'next/server'; + import { type RouteLayoutParams, getStaticSiteContext } from '@/app/utils'; import { serveAskMarkdown } from '@/routes/markdownAsk'; -import type { NextRequest } from 'next/server'; export const dynamic = 'force-static'; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown-ask/[question]/route.ts b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown-ask/[question]/route.ts index 972bc0a95..ac01394f8 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown-ask/[question]/route.ts +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown-ask/[question]/route.ts @@ -1,6 +1,7 @@ +import type { NextRequest } from 'next/server'; + import { type RouteLayoutParams, getStaticSiteContext } from '@/app/utils'; import { serveAskMarkdown } from '@/routes/markdownAsk'; -import type { NextRequest } from 'next/server'; export const dynamic = 'force-static'; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown/[pagePath]/route.ts b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown/[pagePath]/route.ts index 1631baf31..1a2981bd6 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown/[pagePath]/route.ts +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/markdown/[pagePath]/route.ts @@ -1,6 +1,7 @@ +import type { NextRequest } from 'next/server'; + import { type RouteParams, getPagePathFromParams, getStaticSiteContext } from '@/app/utils'; import { servePageMarkdown } from '@/routes/markdownPage'; -import type { NextRequest } from 'next/server'; export const dynamic = 'force-static'; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/rss/[pagePath]/route.ts b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/rss/[pagePath]/route.ts index 9499facf7..454ad5b19 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/rss/[pagePath]/route.ts +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/rss/[pagePath]/route.ts @@ -1,6 +1,7 @@ +import type { NextRequest } from 'next/server'; + import { type RouteParams, getPagePathFromParams, getStaticSiteContext } from '@/app/utils'; import { servePageRSS } from '@/routes/rss'; -import type { NextRequest } from 'next/server'; export const dynamic = 'force-static'; diff --git a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/site-index/route.ts b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/site-index/route.ts index 7eeffbb1f..62f7068b6 100644 --- a/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/site-index/route.ts +++ b/packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/site-index/route.ts @@ -1,10 +1,11 @@ +import type { NextRequest } from 'next/server'; + import { type RevisionPage, type RevisionPageDocument, type RevisionPageGroup, SiteVisibility, } from '@gitbook/api'; -import type { NextRequest } from 'next/server'; import { type RouteLayoutParams, getStaticSiteContext } from '@/app/utils'; import { throwIfDataError } from '@/lib/data'; diff --git a/packages/gitbook/src/app/utils.ts b/packages/gitbook/src/app/utils.ts index ca0c944fc..727105cd7 100644 --- a/packages/gitbook/src/app/utils.ts +++ b/packages/gitbook/src/app/utils.ts @@ -1,11 +1,13 @@ -import { getVisitorAuthClaims, getVisitorAuthClaimsFromToken } from '@/lib/adaptive'; -import { type SiteURLData, fetchSiteContextByURLLookup, getBaseContext } from '@/lib/context'; -import { getDynamicCustomizationSettings } from '@/lib/customization'; -import type { SiteAPIToken } from '@gitbook/api'; import { jwtDecode } from 'jwt-decode'; import { forbidden, notFound } from 'next/navigation'; import rison from 'rison'; +import type { SiteAPIToken } from '@gitbook/api'; + +import { getVisitorAuthClaims, getVisitorAuthClaimsFromToken } from '@/lib/adaptive'; +import { type SiteURLData, fetchSiteContextByURLLookup, getBaseContext } from '@/lib/context'; +import { getDynamicCustomizationSettings } from '@/lib/customization'; + export type RouteParamMode = 'url-host' | 'url'; export type RouteLayoutParams = { diff --git a/packages/gitbook/src/app/~gitbook/revalidate/route.ts b/packages/gitbook/src/app/~gitbook/revalidate/route.ts index 410c2347d..ebb769785 100644 --- a/packages/gitbook/src/app/~gitbook/revalidate/route.ts +++ b/packages/gitbook/src/app/~gitbook/revalidate/route.ts @@ -1,8 +1,8 @@ +import { revalidateTag } from 'next/cache'; import { type NextRequest, NextResponse } from 'next/server'; import { getLogger } from '@/lib/logger'; import { withVerifySignature } from '@/lib/routes'; -import { revalidateTag } from 'next/cache'; interface JsonBody { tags: string[]; diff --git a/packages/gitbook/src/app/~space/[spaceId]/~gitbook/pdf/page.tsx b/packages/gitbook/src/app/~space/[spaceId]/~gitbook/pdf/page.tsx index fdf11e13d..1418af01e 100644 --- a/packages/gitbook/src/app/~space/[spaceId]/~gitbook/pdf/page.tsx +++ b/packages/gitbook/src/app/~space/[spaceId]/~gitbook/pdf/page.tsx @@ -1,11 +1,7 @@ import { type SpacePDFRouteParams, getSpacePDFContext } from '@/app/~space/[spaceId]/pdf'; import { PDFPage, generatePDFMetadata } from '@/components/PDF'; -export async function generateMetadata({ - params, -}: { - params: Promise; -}) { +export async function generateMetadata({ params }: { params: Promise }) { const context = await getSpacePDFContext(await params); return generatePDFMetadata(context); } diff --git a/packages/gitbook/src/components/AI/controls/ConfirmControl.tsx b/packages/gitbook/src/components/AI/controls/ConfirmControl.tsx index 0daed3288..747e5297d 100644 --- a/packages/gitbook/src/components/AI/controls/ConfirmControl.tsx +++ b/packages/gitbook/src/components/AI/controls/ConfirmControl.tsx @@ -1,10 +1,11 @@ 'use client'; -import { Button } from '@/components/primitives'; -import { tString, useLanguage } from '@/intl/client'; import { z } from 'zod'; + import { AIToolContainer } from './common'; import { type GetAIControlProps, createAIControl } from './helpers'; +import { Button } from '@/components/primitives'; +import { tString, useLanguage } from '@/intl/client'; export const ConfirmControlOutputSchema = z.object({ result: z.enum(['confirmed', 'cancelled']), diff --git a/packages/gitbook/src/components/AI/controls/MultiChoiceControl.tsx b/packages/gitbook/src/components/AI/controls/MultiChoiceControl.tsx index 949a9e1f7..d8d036f22 100644 --- a/packages/gitbook/src/components/AI/controls/MultiChoiceControl.tsx +++ b/packages/gitbook/src/components/AI/controls/MultiChoiceControl.tsx @@ -1,14 +1,16 @@ 'use client'; +import * as React from 'react'; +import { z } from 'zod'; + +import { Icon, IconStyle } from '@gitbook/icons'; + +import { AIToolContainer } from './common'; +import { type GetAIControlProps, createAIControl } from './helpers'; import { Button, Input } from '@/components/primitives'; import { ScrollContainer } from '@/components/primitives/ScrollContainer'; import { tString, useLanguage } from '@/intl/client'; import { tcls } from '@/lib/tailwind'; -import { Icon, IconStyle } from '@gitbook/icons'; -import * as React from 'react'; -import { z } from 'zod'; -import { AIToolContainer } from './common'; -import { type GetAIControlProps, createAIControl } from './helpers'; const OTHER_OPTION_ID = '$other'; @@ -98,7 +100,7 @@ function MultiChoiceControl(props: GetAIControlProps -

{prompt}

+

{prompt}

{options.map((option) => { const isSelected = selectedIds.includes(option.id); return ( @@ -136,9 +138,9 @@ function MultiChoiceControl(props: GetAIControlProps -

{option.label}

+

{option.label}

{option.description ? ( -

+

{option.description}

) : null} diff --git a/packages/gitbook/src/components/AI/controls/SingleChoiceControl.tsx b/packages/gitbook/src/components/AI/controls/SingleChoiceControl.tsx index 17739f104..a69bd454c 100644 --- a/packages/gitbook/src/components/AI/controls/SingleChoiceControl.tsx +++ b/packages/gitbook/src/components/AI/controls/SingleChoiceControl.tsx @@ -1,13 +1,14 @@ 'use client'; +import * as React from 'react'; +import { z } from 'zod'; + +import { AIToolContainer } from './common'; +import { type GetAIControlProps, createAIControl } from './helpers'; import { Button, Input } from '@/components/primitives'; import { ScrollContainer } from '@/components/primitives/ScrollContainer'; import { tString, useLanguage } from '@/intl/client'; import { tcls } from '@/lib/tailwind'; -import * as React from 'react'; -import { z } from 'zod'; -import { AIToolContainer } from './common'; -import { type GetAIControlProps, createAIControl } from './helpers'; const OTHER_OPTION_ID = '$other'; @@ -81,7 +82,7 @@ function SingleChoiceControl(props: GetAIControlProps -

{prompt}

+

{prompt}

{options.map((option) => { const isSelected = selectedId === option.id; @@ -119,9 +120,9 @@ function SingleChoiceControl(props: GetAIControlProps
-

{option.label}

+

{option.label}

{option.description ? ( -

+

{option.description}

) : null} diff --git a/packages/gitbook/src/components/AI/controls/common.tsx b/packages/gitbook/src/components/AI/controls/common.tsx index ef4eeda29..829beff6e 100644 --- a/packages/gitbook/src/components/AI/controls/common.tsx +++ b/packages/gitbook/src/components/AI/controls/common.tsx @@ -1,6 +1,7 @@ -import { tcls } from '@/lib/tailwind'; import type { ComponentPropsWithRef } from 'react'; +import { tcls } from '@/lib/tailwind'; + export function AIToolContainer(props: ComponentPropsWithRef<'div'>) { return (
; diff --git a/packages/gitbook/src/components/AI/navigation.test.ts b/packages/gitbook/src/components/AI/navigation.test.ts index 4f2bc0d5a..16e2691d0 100644 --- a/packages/gitbook/src/components/AI/navigation.test.ts +++ b/packages/gitbook/src/components/AI/navigation.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'bun:test'; + import { normalizePathname, resolveNavigationTarget, toInSiteHref } from './navigation'; const location = { diff --git a/packages/gitbook/src/components/AI/navigation.ts b/packages/gitbook/src/components/AI/navigation.ts index 478a068b8..8adc0b585 100644 --- a/packages/gitbook/src/components/AI/navigation.ts +++ b/packages/gitbook/src/components/AI/navigation.ts @@ -1,5 +1,5 @@ -import { withTrailingSlash } from '@/lib/paths'; import { isExternalLink } from '../utils/link'; +import { withTrailingSlash } from '@/lib/paths'; /** * Resolve a link into a same-site target to navigate to. diff --git a/packages/gitbook/src/components/AI/references.test.ts b/packages/gitbook/src/components/AI/references.test.ts index 8db6ac8eb..1e9c839a2 100644 --- a/packages/gitbook/src/components/AI/references.test.ts +++ b/packages/gitbook/src/components/AI/references.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'bun:test'; + import { type AIChatReference, serializeReferences } from './references'; describe('serializeReferences', () => { diff --git a/packages/gitbook/src/components/AI/server-actions/AIMessageView.tsx b/packages/gitbook/src/components/AI/server-actions/AIMessageView.tsx index 4fd4c55b8..9370547d9 100644 --- a/packages/gitbook/src/components/AI/server-actions/AIMessageView.tsx +++ b/packages/gitbook/src/components/AI/server-actions/AIMessageView.tsx @@ -1,10 +1,11 @@ -import { CollapsibleContent } from '@/components/primitives'; -import type { GitBookSiteContext } from '@/lib/context'; -import { tcls } from '@/lib/tailwind'; import { type AIMessage, AIMessageStepPhase } from '@gitbook/api'; + import { DocumentView } from '../../DocumentView'; import { AIToolCallsSummary } from './AIToolCallsSummary'; import type { RenderAIMessageOptions } from './types'; +import { CollapsibleContent } from '@/components/primitives'; +import type { GitBookSiteContext } from '@/lib/context'; +import { tcls } from '@/lib/tailwind'; /** * Render a message from the API backend. diff --git a/packages/gitbook/src/components/AI/server-actions/AIToolCallsSummary.tsx b/packages/gitbook/src/components/AI/server-actions/AIToolCallsSummary.tsx index 1a9307e7d..6d5e2ddd8 100644 --- a/packages/gitbook/src/components/AI/server-actions/AIToolCallsSummary.tsx +++ b/packages/gitbook/src/components/AI/server-actions/AIToolCallsSummary.tsx @@ -1,11 +1,6 @@ -import { HighlightQuery } from '@/components/Search/HighlightQuery'; -import { Link, StyledLink } from '@/components/primitives'; -import { Favicon } from '@/components/utils'; -import { getSpaceLanguage } from '@/intl/server'; -import { t } from '@/intl/translate'; -import type { GitBookSiteContext } from '@/lib/context'; -import { resolveContentRef } from '@/lib/references'; -import { tcls } from '@/lib/tailwind'; +import assertNever from 'assert-never'; +import type * as React from 'react'; + import type { AIToolCall, AIToolCallGetPageContent, @@ -16,8 +11,15 @@ import type { ContentRef, } from '@gitbook/api'; import { Icon, type IconName } from '@gitbook/icons'; -import assertNever from 'assert-never'; -import type * as React from 'react'; + +import { Link, StyledLink } from '@/components/primitives'; +import { HighlightQuery } from '@/components/Search/HighlightQuery'; +import { Favicon } from '@/components/utils'; +import { getSpaceLanguage } from '@/intl/server'; +import { t } from '@/intl/translate'; +import type { GitBookSiteContext } from '@/lib/context'; +import { resolveContentRef } from '@/lib/references'; +import { tcls } from '@/lib/tailwind'; /** * Display the tool calls in a message or step. @@ -43,7 +45,7 @@ function ToolCallSummary(props: { toolCall: AIToolCall; context: GitBookSiteCont return (
0; return ( -
+

{t(language, 'searched_for', {toolCall.query})}

-

+

{hasResults ? t(language, 'search_results_count', toolCall.results.length) : t(language, 'search_no_results')} @@ -204,7 +206,7 @@ async function DescriptionForSearchToolCall(props: { ) : null}

{hasResults ? ( -
+
    {searchResultsWithHrefs.map((result, index) => { const resultKey = (() => { @@ -231,7 +233,7 @@ async function DescriptionForSearchToolCall(props: { > -

    +

    {result.description && ( -

    +

    - + @@ -133,10 +135,7 @@ export function AIChat() { /** * Dynamic icon to indicate the AI chat state. */ -export function AIChatDynamicIcon(props: { - trademark: boolean; - className?: string; -}) { +export function AIChatDynamicIcon(props: { trademark: boolean; className?: string }) { const { trademark, className } = props; const chat = useAIChatState(); const status = getAIChatStatus(chat); @@ -153,9 +152,7 @@ export function AIChatDynamicIcon(props: { /** * Subtitle of the AI chat window. */ -export function AIChatSubtitle(props: { - chat: AIChatState; -}) { +export function AIChatSubtitle(props: { chat: AIChatState }) { const { chat } = props; const language = useLanguage(); const status = getAIChatStatus(chat); @@ -236,7 +233,7 @@ export function AIChatBody(props: { return ( <>

    {greeting?.title || timeGreeting}

    @@ -285,7 +282,7 @@ export function AIChatBody(props: { )} -

    +
    {!chat.error && chat.queuedMessages.map((message, index) => ( +
    {t(language, 'ai_chat_error')} diff --git a/packages/gitbook/src/components/AIChat/AIChatButton.tsx b/packages/gitbook/src/components/AIChat/AIChatButton.tsx index abf533d34..22e3cd89f 100644 --- a/packages/gitbook/src/components/AIChat/AIChatButton.tsx +++ b/packages/gitbook/src/components/AIChat/AIChatButton.tsx @@ -1,10 +1,10 @@ 'use client'; -import { useLanguage } from '@/intl/client'; -import { t, tString } from '@/intl/translate'; import type { Assistant } from '../AI'; import { useIsMobile } from '../hooks/useIsMobile'; import { Button } from '../primitives'; import { KeyboardShortcut } from '../primitives/KeyboardShortcut'; +import { useLanguage } from '@/intl/client'; +import { t, tString } from '@/intl/translate'; const MOBILE_BREAKPOINT = 688; // 43rem, equal to Tailwind's @max-2xl container breakpoint diff --git a/packages/gitbook/src/components/AIChat/AIChatControlButton.tsx b/packages/gitbook/src/components/AIChat/AIChatControlButton.tsx index b71f3a9f3..a07e9c3db 100644 --- a/packages/gitbook/src/components/AIChat/AIChatControlButton.tsx +++ b/packages/gitbook/src/components/AIChat/AIChatControlButton.tsx @@ -1,9 +1,9 @@ 'use client'; -import { useLanguage } from '@/intl/client'; -import { t } from '@/intl/translate'; import { useAIChatController, useAIChatState } from '../AI'; import { Button } from '../primitives'; +import { useLanguage } from '@/intl/client'; +import { t } from '@/intl/translate'; /** * Button to control the chat (clear, etc.) diff --git a/packages/gitbook/src/components/AIChat/AIChatExpandButton.tsx b/packages/gitbook/src/components/AIChat/AIChatExpandButton.tsx index 11d12c7da..1ca747043 100644 --- a/packages/gitbook/src/components/AIChat/AIChatExpandButton.tsx +++ b/packages/gitbook/src/components/AIChat/AIChatExpandButton.tsx @@ -1,9 +1,10 @@ 'use client'; -import { tString, useLanguage } from '@/intl/client'; import { Icon } from '@gitbook/icons'; + import { Button } from '../primitives'; import { useAIChatWidthStore, useIsAIChatMaxWidth } from './useAIChatWidthStore'; +import { tString, useLanguage } from '@/intl/client'; export function AIChatExpandButton() { const language = useLanguage(); diff --git a/packages/gitbook/src/components/AIChat/AIChatIcon.tsx b/packages/gitbook/src/components/AIChat/AIChatIcon.tsx index 90e2825f3..1da741e3b 100644 --- a/packages/gitbook/src/components/AIChat/AIChatIcon.tsx +++ b/packages/gitbook/src/components/AIChat/AIChatIcon.tsx @@ -1,6 +1,7 @@ -import { Icon, IconStyle } from '@gitbook/icons'; import type React from 'react'; +import { Icon, IconStyle } from '@gitbook/icons'; + interface AIChatIconProps extends React.SVGProps { className?: string; size?: number; diff --git a/packages/gitbook/src/components/AIChat/AIChatInput.tsx b/packages/gitbook/src/components/AIChat/AIChatInput.tsx index c2ca5f379..e9548c81c 100644 --- a/packages/gitbook/src/components/AIChat/AIChatInput.tsx +++ b/packages/gitbook/src/components/AIChat/AIChatInput.tsx @@ -1,12 +1,14 @@ -import { t, tString, useLanguage } from '@/intl/client'; -import { tcls } from '@/lib/tailwind'; -import { Icon } from '@gitbook/icons'; import { useEffect, useRef, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; + +import { Icon } from '@gitbook/icons'; + import { useAIChatController, useAIChatState } from '../AI/useAIChat'; import { HoverCard, HoverCardRoot, HoverCardTrigger } from '../primitives'; import { Input } from '../primitives/Input'; import { AIChatReferenceChips } from './AIChatReferenceChips'; +import { t, tString, useLanguage } from '@/intl/client'; +import { tcls } from '@/lib/tailwind'; export function AIChatInput(props: { disabled?: boolean; @@ -150,8 +152,8 @@ export function AIChatInput(props: { {/* Negative margin to compensate for Input's padding, so the badge appears flush with the cursor */} -
    - +
    + {t(language, 'ai_chat_context_badge')} {' '} diff --git a/packages/gitbook/src/components/AIChat/AIChatMessages.tsx b/packages/gitbook/src/components/AIChat/AIChatMessages.tsx index ff99c0cbd..6913d3204 100644 --- a/packages/gitbook/src/components/AIChat/AIChatMessages.tsx +++ b/packages/gitbook/src/components/AIChat/AIChatMessages.tsx @@ -1,8 +1,7 @@ -import { useLanguage } from '@/intl/client'; -import { t, tString } from '@/intl/translate'; -import { tcls } from '@/lib/tailwind'; -import { AIMessageRole } from '@gitbook/api'; import { Fragment } from 'react'; + +import { AIMessageRole } from '@gitbook/api'; + import { type AIChatController, type AIChatMessage, @@ -12,22 +11,22 @@ import { import { ToggleChevron } from '../primitives'; import { Button } from '../primitives/Button'; import { Collapsible, CollapsibleTrigger } from '../primitives/Collapsible'; +import { AIChatFollowupSuggestions } from './AiChatFollowupSuggestions'; import { AIChatReferenceChips } from './AIChatReferenceChips'; import { AIResponseFeedback } from './AIResponseFeedback'; -import { AIChatFollowupSuggestions } from './AiChatFollowupSuggestions'; +import { useLanguage } from '@/intl/client'; +import { t, tString } from '@/intl/translate'; +import { tcls } from '@/lib/tailwind'; -export function AIChatMessages(props: { - chat: AIChatState; - chatController: AIChatController; -}) { +export function AIChatMessages(props: { chat: AIChatState; chatController: AIChatController }) { const { chat, chatController } = props; const status = getAIChatStatus(chat); const showLoadingShim = chat.responding && status !== 'working' && status !== 'done'; // Group messages: user messages start a new group, all following messages until next user message belong to that group type MessageGroup = { message: AIChatMessage; originalIndex: number }; - const messageGroups: Array> = []; - let currentGroup: Array = []; + const messageGroups: MessageGroup[][] = []; + let currentGroup: MessageGroup[] = []; chat.messages.forEach((message, index) => { if (message.role === AIMessageRole.User) { @@ -136,7 +135,7 @@ export function AIChatMessages(props: { variant="blank" size="small" label={tString(language, 'ai_chat_view_activity')} - className="-mx-3 -my-1.5 group/dropdown animate-blur-in-display-slow self-start" + className="group/dropdown animate-blur-in-display-slow -mx-3 -my-1.5 self-start" >
    @@ -213,7 +212,10 @@ export function AIChatMessages(props: { export function HoldMessage({ breakLines = false, className, -}: { breakLines?: boolean; className?: string }) { +}: { + breakLines?: boolean; + className?: string; +}) { const language = useLanguage(); return ( @@ -260,7 +262,7 @@ function LoadingSkeleton() { {Array.from({ length: 7 }).map((_, index) => (
    +
    {ref.type === 'page' && ref.href ? ( // A page reference may be clicked from anywhere: render a link so it @@ -80,7 +81,7 @@ export function AIChatReferenceChips(props: { onRemove(ref.id); }} disabled={disabled} - className="inline-flex size-4 shrink-0 items-center justify-center circular-corners:rounded-full rounded-corners:rounded-sm text-tint/8 transition hover:bg-tint hover:text-tint-strong disabled:cursor-not-allowed disabled:opacity-5" + className="inline-flex size-4 shrink-0 items-center justify-center text-tint/8 transition hover:bg-tint hover:text-tint-strong disabled:cursor-not-allowed disabled:opacity-5 rounded-corners:rounded-sm circular-corners:rounded-full" > diff --git a/packages/gitbook/src/components/AIChat/AIChatResizeHandle.tsx b/packages/gitbook/src/components/AIChat/AIChatResizeHandle.tsx index 6c61faf31..930cd38fb 100644 --- a/packages/gitbook/src/components/AIChat/AIChatResizeHandle.tsx +++ b/packages/gitbook/src/components/AIChat/AIChatResizeHandle.tsx @@ -1,8 +1,9 @@ 'use client'; -import { tcls } from '@/lib/tailwind'; import React from 'react'; + import { useAIChatWidthStore } from './useAIChatWidthStore'; +import { tcls } from '@/lib/tailwind'; function setResizing(active: boolean) { document.documentElement.dataset.aiChatResizing = String(active); @@ -77,7 +78,7 @@ export function AIChatResizeHandle() { 'items-stretch justify-center' )} > - +
    ); } diff --git a/packages/gitbook/src/components/AIChat/AIChatSuggestedQuestions.tsx b/packages/gitbook/src/components/AIChat/AIChatSuggestedQuestions.tsx index 0ad59d763..649dd2e88 100644 --- a/packages/gitbook/src/components/AIChat/AIChatSuggestedQuestions.tsx +++ b/packages/gitbook/src/components/AIChat/AIChatSuggestedQuestions.tsx @@ -1,8 +1,8 @@ +import type { AIChatController } from '../AI'; +import { Button } from '../primitives'; +import { useRecentSearchQueries } from '../Search/recent-queries'; import { useCurrentContent } from '@/components/hooks'; import { tString, useLanguage } from '@/intl/client'; -import type { AIChatController } from '../AI'; -import { useRecentSearchQueries } from '../Search/recent-queries'; -import { Button } from '../primitives'; export default function AIChatSuggestedQuestions(props: { chatController: AIChatController; @@ -47,7 +47,7 @@ export default function AIChatSuggestedQuestions(props: { key={question} variant="blank" size="small" - className="max-w-full animate-blur-in-slow border-none bg-primary-solid/1 hover:bg-primary-hover" + className="animate-blur-in-slow max-w-full border-none bg-primary-solid/1 hover:bg-primary-hover" style={{ animationDelay: `${1000 + index * 100}ms`, }} diff --git a/packages/gitbook/src/components/AIChat/AIResponseFeedback.tsx b/packages/gitbook/src/components/AIChat/AIResponseFeedback.tsx index a93857288..6048b0762 100644 --- a/packages/gitbook/src/components/AIChat/AIResponseFeedback.tsx +++ b/packages/gitbook/src/components/AIChat/AIResponseFeedback.tsx @@ -1,11 +1,12 @@ 'use client'; +import { useState } from 'react'; + +import { useTrackEvent } from '../Insights'; +import { Button } from '../primitives'; import { useLanguage } from '@/intl/client'; import { t, tString } from '@/intl/translate'; import { type ClassValue, tcls } from '@/lib/tailwind'; -import { useState } from 'react'; -import { useTrackEvent } from '../Insights'; -import { Button } from '../primitives'; export function AIResponseFeedback(props: { className?: ClassValue; @@ -61,7 +62,7 @@ export function AIResponseFeedback(props: { /> {rating !== null ? ( {t(language, 'was_this_helpful_thank_you')} diff --git a/packages/gitbook/src/components/AIChat/AiChatFollowupSuggestions.tsx b/packages/gitbook/src/components/AIChat/AiChatFollowupSuggestions.tsx index 1346eef52..ea1b80082 100644 --- a/packages/gitbook/src/components/AIChat/AiChatFollowupSuggestions.tsx +++ b/packages/gitbook/src/components/AIChat/AiChatFollowupSuggestions.tsx @@ -29,7 +29,7 @@ export function AIChatFollowupSuggestions(props: { chatController.postMessage({ message: suggestion }); }} label={suggestion} - className="starting:h-0 max-w-full origin-left animate-blur-in-slow border-none bg-primary-11/1 starting:py-0 text-left transition-all transition-discrete duration-500 hover:bg-primary-hover" + className="starting:h-0 animate-blur-in-slow starting:py-0 transition-discrete max-w-full origin-left border-none bg-primary-11/1 text-left transition-all duration-500 hover:bg-primary-hover" size="small" variant="blank" style={{ diff --git a/packages/gitbook/src/components/AIChat/AskAIParagraphButton/AskAIParagraphButton.tsx b/packages/gitbook/src/components/AIChat/AskAIParagraphButton/AskAIParagraphButton.tsx index 0df53f8cc..b71b3d2bc 100644 --- a/packages/gitbook/src/components/AIChat/AskAIParagraphButton/AskAIParagraphButton.tsx +++ b/packages/gitbook/src/components/AIChat/AskAIParagraphButton/AskAIParagraphButton.tsx @@ -2,13 +2,12 @@ import fnv1a from '@sindresorhus/fnv1a'; +import { AIChatIcon } from '../AIChatIcon'; import { useAIChatController, useAIConfig } from '@/components/AI'; import { Button } from '@/components/primitives'; import { t, tString, useLanguage } from '@/intl/client'; import { type ClassValue, tcls } from '@/lib/tailwind'; -import { AIChatIcon } from '../AIChatIcon'; - /** * A small icon button revealed in the left margin of a top-level paragraph when it is hovered (on * devices with a fine pointer). Clicking it stages the paragraph's text as a reference and opens diff --git a/packages/gitbook/src/components/AIChat/AskAITextSelection/AskAITextSelection.tsx b/packages/gitbook/src/components/AIChat/AskAITextSelection/AskAITextSelection.tsx index ee3d00909..ba6db9854 100644 --- a/packages/gitbook/src/components/AIChat/AskAITextSelection/AskAITextSelection.tsx +++ b/packages/gitbook/src/components/AIChat/AskAITextSelection/AskAITextSelection.tsx @@ -5,6 +5,8 @@ import { AnimatePresence, motion } from 'motion/react'; import * as React from 'react'; import { createPortal } from 'react-dom'; +import { AIChatIcon } from '../AIChatIcon'; +import { useStableTextSelection } from './useStableTextSelection'; import { useAIChatController, useAIConfig } from '@/components/AI'; import { useIsMobile } from '@/components/hooks/useIsMobile'; import { useIsMounted } from '@/components/hooks/useIsMounted'; @@ -12,9 +14,6 @@ import { Button } from '@/components/primitives'; import { isAIChatEnabled } from '@/components/utils/isAIChatEnabled'; import { t, useLanguage } from '@/intl/client'; -import { AIChatIcon } from '../AIChatIcon'; -import { useStableTextSelection } from './useStableTextSelection'; - /** Gap between the selection and the button. */ const GAP = 8; /** Minimum distance to the viewport edges. */ diff --git a/packages/gitbook/src/components/AIChat/useAIChatWidthStore.ts b/packages/gitbook/src/components/AIChat/useAIChatWidthStore.ts index 79afebf24..a1a533ff5 100644 --- a/packages/gitbook/src/components/AIChat/useAIChatWidthStore.ts +++ b/packages/gitbook/src/components/AIChat/useAIChatWidthStore.ts @@ -1,12 +1,13 @@ 'use client'; +import { create } from 'zustand'; +import { type StorageValue, persist } from 'zustand/middleware'; + import { getLocalStorageItem, removeLocalStorageItem, setLocalStorageItem, } from '@/lib/browser/local-storage'; -import { create } from 'zustand'; -import { type StorageValue, persist } from 'zustand/middleware'; const AI_CHAT_MIN_WIDTH = 384; const AI_CHAT_MAX_WIDTH = 640; diff --git a/packages/gitbook/src/components/Adaptive/AdaptiveVisitorContextProvider.tsx b/packages/gitbook/src/components/Adaptive/AdaptiveVisitorContextProvider.tsx index 1edf65068..91b163246 100644 --- a/packages/gitbook/src/components/Adaptive/AdaptiveVisitorContextProvider.tsx +++ b/packages/gitbook/src/components/Adaptive/AdaptiveVisitorContextProvider.tsx @@ -1,10 +1,12 @@ 'use client'; -import type { GitBookSiteContext } from '@/lib/context'; -import { OpenAPIPrefillContextProvider } from '@gitbook/react-openapi'; import * as React from 'react'; import { createContext, useContext } from 'react'; + +import { OpenAPIPrefillContextProvider } from '@gitbook/react-openapi'; + import type { AdaptiveVisitorClaims } from './types'; +import type { GitBookSiteContext } from '@/lib/context'; /** * In-memory cache of visitor claim readers keyed by contextId. diff --git a/packages/gitbook/src/components/Adaptive/utils.ts b/packages/gitbook/src/components/Adaptive/utils.ts index 304410a7a..b085cacbd 100644 --- a/packages/gitbook/src/components/Adaptive/utils.ts +++ b/packages/gitbook/src/components/Adaptive/utils.ts @@ -1,4 +1,5 @@ import type { Variables } from '@gitbook/api'; + import type { AdaptiveVisitorClaims } from './types'; /** diff --git a/packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx b/packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx index a1c461f85..6d933a9cf 100644 --- a/packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx +++ b/packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx @@ -1,7 +1,7 @@ -import type { GitBookSiteContext } from '@/lib/context'; import { AdminToolbarClient } from './AdminToolbarClient'; import { getToolbarChangedPages } from './changedPages'; import type { AdminToolbarContext } from './types'; +import type { GitBookSiteContext } from '@/lib/context'; export interface AdminToolbarProps { context: GitBookSiteContext; diff --git a/packages/gitbook/src/components/AdminToolbar/AdminToolbarClient.tsx b/packages/gitbook/src/components/AdminToolbar/AdminToolbarClient.tsx index 0988db421..d0fe9a7d9 100644 --- a/packages/gitbook/src/components/AdminToolbar/AdminToolbarClient.tsx +++ b/packages/gitbook/src/components/AdminToolbar/AdminToolbarClient.tsx @@ -1,9 +1,11 @@ 'use client'; -import { Icon } from '@gitbook/icons'; import { MotionConfig, motion } from 'motion/react'; + +import { Icon } from '@gitbook/icons'; + import { useCheckForContentUpdate } from '../AutoRefreshContent'; -import { useVisitor } from '../Insights'; import { useCurrentPagePath } from '../hooks'; +import { useVisitor } from '../Insights'; import { ChangedPagesButton } from './ChangedPagesButton'; import { HideToolbarButton } from './HideToolbarButton'; import { IframeWrapper } from './IframeWrapper'; @@ -225,7 +227,7 @@ function RevisionToolbar(props: ToolbarViewProps) { ) : (
    Setup GitSync to edit using Git{' '} -
    +
    -
    {changedPagesLabel}
    +
    {changedPagesLabel}
    {changedPages.pages.map((page) => ( @@ -112,7 +114,7 @@ export function ChangedPagesButton(props: { {changedPages.more > 0 ? ( <> -
    +
    {changedPages.more} more changes not shown
    @@ -133,14 +135,14 @@ function ChangedPageMenuItem(props: { page: MinimalChangedPage }) { - + {page.title} - + /{page.path || ''} - + {actionLabel} diff --git a/packages/gitbook/src/components/AdminToolbar/HideToolbarButton.tsx b/packages/gitbook/src/components/AdminToolbar/HideToolbarButton.tsx index 577da8e10..c268eaecd 100644 --- a/packages/gitbook/src/components/AdminToolbar/HideToolbarButton.tsx +++ b/packages/gitbook/src/components/AdminToolbar/HideToolbarButton.tsx @@ -1,12 +1,14 @@ 'use client'; -import { tcls } from '@/lib/tailwind'; -import { Icon, type IconName, IconStyle } from '@gitbook/icons'; import { motion } from 'motion/react'; import React, { useRef } from 'react'; import { useOnClickOutside } from 'usehooks-ts'; + +import { Icon, type IconName, IconStyle } from '@gitbook/icons'; + import { ToolbarButton, type ToolbarButtonProps } from './Toolbar'; import styles from './Toolbar.module.css'; import { useToolbarControls } from './ToolbarControlsContext'; +import { tcls } from '@/lib/tailwind'; // Params for the expanding arc defined separately for easier tweaking. const ARC_PARAMS = { @@ -110,7 +112,7 @@ export function HideToolbarButton(props: HideToolbarButtonProps) { onClick: controls.closePersistent, } : null, - ].filter(Boolean) as Array; + ].filter(Boolean) as ArcMenuItem[]; const sharedMotionStyle = motionValues ? { @@ -124,7 +126,7 @@ export function HideToolbarButton(props: HideToolbarButtonProps) { title={open ? undefined : 'Hide toolbar'} className={ open || closing - ? 'border-[0.5px] border-neutral-5 border-solid dark:border-neutral-8' + ? 'border-[0.5px] border-solid border-neutral-5 dark:border-neutral-8' : undefined } onClick={() => { diff --git a/packages/gitbook/src/components/AdminToolbar/Toolbar.tsx b/packages/gitbook/src/components/AdminToolbar/Toolbar.tsx index 93f70d63a..7cd4901e2 100644 --- a/packages/gitbook/src/components/AdminToolbar/Toolbar.tsx +++ b/packages/gitbook/src/components/AdminToolbar/Toolbar.tsx @@ -7,15 +7,16 @@ import { useSpring, } from 'motion/react'; import React, { isValidElement } from 'react'; + +import { Icon, type IconName, IconStyle } from '@gitbook/icons'; + +import { Tooltip } from '../primitives'; import { AnimatedLogo } from './AnimatedLogo'; import { useToolbarControls } from './ToolbarControlsContext'; import { ToolbarVisibilityHint } from './ToolbarVisibilityHint'; - -import { tcls } from '@/lib/tailwind'; -import { Icon, type IconName, IconStyle } from '@gitbook/icons'; -import { Tooltip } from '../primitives'; import { getCopyVariants, toolbarEasings } from './transitions'; import { useMagnificationEffect } from './useMagnificationEffect'; +import { tcls } from '@/lib/tailwind'; const DURATION_LOGO_APPEARANCE = 2000; const DELAY_BETWEEN_LOGO_AND_CONTENT = 100; @@ -107,7 +108,7 @@ export function Toolbar(props: ToolbarProps) { return ( @@ -182,7 +183,7 @@ export function ToolbarButtonGroup(props: { children: React.ReactNode }) { variants={toolbarEasings.parent} initial="hidden" animate="show" - className="flex items-center gap-1 overflow-visible pr-2 pl-4" + className="flex items-center gap-1 overflow-visible pl-4 pr-2" > {buttonChildren.map((child, index) => { const childEl = child as React.ReactElement; @@ -318,7 +319,7 @@ function ToolbarButtonWrapper(props: { export function ToolbarTitle(props: { prefix?: string; suffix: string }) { return ( -
    +
    {props.prefix ? : null}
    @@ -351,7 +352,7 @@ export function ToolbarSubtitle(props: { subtitle: React.ReactNode }) { return ( {props.subtitle} diff --git a/packages/gitbook/src/components/AdminToolbar/ToolbarDate.tsx b/packages/gitbook/src/components/AdminToolbar/ToolbarDate.tsx index aa2c021cd..312a4fee2 100644 --- a/packages/gitbook/src/components/AdminToolbar/ToolbarDate.tsx +++ b/packages/gitbook/src/components/AdminToolbar/ToolbarDate.tsx @@ -1,10 +1,10 @@ 'use client'; +import { AnimatePresence, motion } from 'motion/react'; import React from 'react'; -import { useLanguage } from '@/intl/client'; -import { AnimatePresence, motion } from 'motion/react'; import { formatDateTime, formatDateWeekday, formatRelative } from '../utils/dates'; +import { useLanguage } from '@/intl/client'; type DateFormat = 'relative' | 'weekday' | 'full'; diff --git a/packages/gitbook/src/components/AdminToolbar/ToolbarVisibilityHint.tsx b/packages/gitbook/src/components/AdminToolbar/ToolbarVisibilityHint.tsx index cc98a24cd..9db15ae0c 100644 --- a/packages/gitbook/src/components/AdminToolbar/ToolbarVisibilityHint.tsx +++ b/packages/gitbook/src/components/AdminToolbar/ToolbarVisibilityHint.tsx @@ -2,6 +2,7 @@ import { motion } from 'motion/react'; import React from 'react'; + import { getVisibilityHintDismissed, setVisibilityHintDismissed } from './utils'; interface ToolbarVisibilityHintProps { @@ -30,14 +31,14 @@ export function ToolbarVisibilityHint(props: ToolbarVisibilityHintProps) { stiffness: 300, damping: 25, }} - className="-translate-x-1/2 pointer-events-none absolute bottom-[calc(100%+2px)] left-1/2 flex items-center gap-1.5 rounded-lg border border-[#eaeaea] border-solid bg-white py-1 pr-1 pl-2" + className="pointer-events-none absolute bottom-[calc(100%+2px)] left-1/2 flex -translate-x-1/2 items-center gap-1.5 rounded-lg border border-solid border-[#eaeaea] bg-white py-1 pl-2 pr-1" > This toolbar appears only when signed in to GitBook.
    {rating ? ( submitted ? ( -
    +
    {tString(languages, 'was_this_helpful_thank_you')}
    diff --git a/packages/gitbook/src/components/RootLayout/CustomizationRootLayout.tsx b/packages/gitbook/src/components/RootLayout/CustomizationRootLayout.tsx index bf7878295..a7af18e60 100644 --- a/packages/gitbook/src/components/RootLayout/CustomizationRootLayout.tsx +++ b/packages/gitbook/src/components/RootLayout/CustomizationRootLayout.tsx @@ -1,3 +1,5 @@ +import * as ReactDOM from 'react-dom'; + import { CustomizationDefaultThemeMode, CustomizationSidebarBackgroundStyle, @@ -19,17 +21,16 @@ import { hexToRgb, } from '@gitbook/colors'; import { IconsProvider } from '@gitbook/icons'; -import * as ReactDOM from 'react-dom'; +import { AnnouncementDismissedScript } from '../Announcement'; +import { SelectStateScript } from '../Select'; +import { OperatingSystemClassScript } from './OperatingSystemClassScript'; +import { RootLayoutClientContexts } from './RootLayoutClientContexts'; import { type FontData, getFontData } from '@/fonts'; import { fontNotoColorEmoji, fonts } from '@/fonts/default'; +import './globals.css'; import { getContentLocale, getSpaceLanguage } from '@/intl/server'; import { getAssetURL } from '@/lib/assets'; -import { tcls } from '@/lib/tailwind'; - -import { RootLayoutClientContexts } from './RootLayoutClientContexts'; - -import './globals.css'; import type { GitBookAnyContext } from '@/lib/context'; import { GITBOOK_FONTS_URL, GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@/lib/env'; import { @@ -38,10 +39,8 @@ import { getDefaultInlineIconSourceRequests, getInlineIconSources, } from '@/lib/icons/inline'; +import { tcls } from '@/lib/tailwind'; import { defaultCustomization } from '@/lib/utils'; -import { AnnouncementDismissedScript } from '../Announcement'; -import { SelectStateScript } from '../Select'; -import { OperatingSystemClassScript } from './OperatingSystemClassScript'; function preloadFont(fontData: FontData) { if (fontData.type === 'custom') { diff --git a/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx b/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx index c7c2b9fed..f231e2ee6 100644 --- a/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx +++ b/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx @@ -1,12 +1,12 @@ 'use client'; +import { TooltipProvider } from '@radix-ui/react-tooltip'; import type React from 'react'; -import { TranslateContext } from '@/intl/client'; -import type { TranslationLanguage } from '@/intl/translations'; -import { TooltipProvider } from '@radix-ui/react-tooltip'; import { NavigationStatusProvider, ScrollPage } from '../hooks'; import { LoadingStateProvider } from '../primitives/LoadingStateProvider'; +import { TranslateContext } from '@/intl/client'; +import type { TranslationLanguage } from '@/intl/translations'; /** * Client component context providers for the root layout. diff --git a/packages/gitbook/src/components/Search/LocalSearchResults.tsx b/packages/gitbook/src/components/Search/LocalSearchResults.tsx index 84bdd432c..c12f32acc 100644 --- a/packages/gitbook/src/components/Search/LocalSearchResults.tsx +++ b/packages/gitbook/src/components/Search/LocalSearchResults.tsx @@ -1,10 +1,11 @@ 'use client'; -import { tcls } from '@/lib/tailwind'; import { Icon, type IconName } from '@gitbook/icons'; + import { Emoji } from '../primitives/Emoji/Emoji'; import { Link } from '../primitives/Link'; import type { LocalPageResult } from './useLocalSearchResults'; +import { tcls } from '@/lib/tailwind'; /** * Renders local search results above the main server results. @@ -80,7 +81,7 @@ function LocalSearchResultCard({ ) : null} -

    +

    {result.title}

    diff --git a/packages/gitbook/src/components/Search/SearchAskAnswer.tsx b/packages/gitbook/src/components/Search/SearchAskAnswer.tsx index de27d9816..120e0a209 100644 --- a/packages/gitbook/src/components/Search/SearchAskAnswer.tsx +++ b/packages/gitbook/src/components/Search/SearchAskAnswer.tsx @@ -1,13 +1,10 @@ 'use client'; -import { useLanguage } from '@/intl/client'; -import { t, tString } from '@/intl/translate'; -import type { TranslationLanguage } from '@/intl/translations'; -import { tcls } from '@/lib/tailwind'; -import { Icon } from '@gitbook/icons'; import { readStreamableValue } from 'ai/rsc'; import React from 'react'; +import { Icon } from '@gitbook/icons'; + import { AIResponseFeedback, AISearchIcon } from '../AIChat'; import { HoldMessage } from '../AIChat/AIChatMessages'; import { getInsightsSession, useTrackEvent } from '../Insights'; @@ -15,6 +12,10 @@ import { Button, Link } from '../primitives'; import { useSearchAskContext } from './SearchAskContext'; import { type AskAnswerResult, type AskAnswerSource, streamAskQuestion } from './server-actions'; import { useSearchLink, useSetSearchState } from './useSearch'; +import { useLanguage } from '@/intl/client'; +import { t, tString } from '@/intl/translate'; +import type { TranslationLanguage } from '@/intl/translations'; +import { tcls } from '@/lib/tailwind'; export type SearchAskState = | { @@ -138,13 +139,13 @@ function TransitionAnswerBody(props: { return display ? (
    -
    +
    + ) + : t(language, 'ai_chat_explored')} + + +
    ) : null} diff --git a/packages/gitbook/src/components/DocumentView/Annotation/AnnotationPopover.tsx b/packages/gitbook/src/components/DocumentView/Annotation/AnnotationPopover.tsx index edb95cb4e..cb5b3e4bb 100644 --- a/packages/gitbook/src/components/DocumentView/Annotation/AnnotationPopover.tsx +++ b/packages/gitbook/src/components/DocumentView/Annotation/AnnotationPopover.tsx @@ -10,12 +10,12 @@ export function AnnotationPopover(props: { children: React.ReactNode; body: Reac return ( | null>(null); const [error, setError] = useState(false); const [isLoading, setIsLoading] = useState(true); - // `isFullscreen` is the open intent; `isExiting` keeps the dialog mounted while it - // animates closed. `isPresent` is true whenever the diagram lives in the dialog. + // `isPresent` stays true through the closing animation, until the panel hands the host back. const [isFullscreen, setIsFullscreen] = useState(false); - const [isExiting, setIsExiting] = useState(false); - const isPresent = isFullscreen || isExiting; + const [isPresent, setIsPresent] = useState(false); const { resolvedTheme } = useTheme(); const darkMode = resolvedTheme === 'dark'; const id = useSafeId(); @@ -123,9 +118,6 @@ export function MermaidCodeBlock( }; }, [source, id, darkMode, mermaidRuntimeURL, shouldRender]); - // Lock the page scroll while the dialog is on screen (handles scrollbar width and iOS). - usePreventScroll({ isDisabled: !isPresent }); - const openFullscreen = useCallback(() => { // Reserve the inline slot's current height before the diagram is detached, so the // page layout does not jump. Measured here while still inline and un-restyled. @@ -133,7 +125,7 @@ export function MermaidCodeBlock( if (root) { root.style.minHeight = `${root.offsetHeight}px`; } - setIsExiting(false); + setIsPresent(true); setIsFullscreen(true); // Re-center the diagram for the larger view. panZoom?.reset(); @@ -141,38 +133,8 @@ export function MermaidCodeBlock( const closeFullscreen = useCallback(() => { setIsFullscreen(false); - setIsExiting(true); }, []); - // Keep the dialog mounted until the exit animation finishes, then unmount it. - useEffect(() => { - if (!isExiting) { - return; - } - - const timer = window.setTimeout(() => { - setIsExiting(false); - panZoom?.reset(); - }, DIALOG_ANIMATION_MS); - return () => window.clearTimeout(timer); - }, [isExiting, panZoom]); - - // Allow Escape to close the dialog. - useEffect(() => { - if (!isFullscreen) { - return; - } - - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape') { - closeFullscreen(); - } - }; - - document.addEventListener('keydown', onKeyDown); - return () => document.removeEventListener('keydown', onKeyDown); - }, [isFullscreen, closeFullscreen]); - // Keep the diagram host in the inline slot on mount (and whenever it isn't in the dialog). useLayoutEffect(() => { const host = diagramHostRef.current; @@ -183,8 +145,7 @@ export function MermaidCodeBlock( }, []); // Move the diagram host into the dialog panel (and back) as the panel mounts/unmounts. - // Done in the panel's ref callback so it happens during commit, before FocusScope reads - // focus. The inline slot's reserved height (set in openFullscreen) is cleared on return. + // The inline slot's reserved height (set in openFullscreen) is cleared on return. const setPanel = useCallback((panel: HTMLDivElement | null) => { panelRef.current = panel; const host = diagramHostRef.current; @@ -198,6 +159,7 @@ export function MermaidCodeBlock( } else if (root) { root.appendChild(host); root.style.minHeight = ''; + setIsPresent(false); } }, []); @@ -253,36 +215,20 @@ export function MermaidCodeBlock( {/* Inline slot: hosts the diagram in the document flow until it goes fullscreen. */}
    {diagramHostRef.current ? createPortal(diagram, diagramHostRef.current) : null} - {isPresent - ? createPortal( - - {/* Backdrop: dims and blurs the page, closes on click. */} - {/* oxlint-disable-next-line jsx-a11y/click-events-have-key-events */} - ); diff --git a/packages/gitbook/src/components/Header/HeaderLinkDropdown.tsx b/packages/gitbook/src/components/Header/HeaderLinkDropdown.tsx index af12696ed..573413937 100644 --- a/packages/gitbook/src/components/Header/HeaderLinkDropdown.tsx +++ b/packages/gitbook/src/components/Header/HeaderLinkDropdown.tsx @@ -16,7 +16,7 @@ import { tcls } from '@/lib/tailwind'; /** * Client component wrapping the DropdownMenu for header links. - * Accepts serializable props to avoid Flight lazy chunk wrappers reaching Radix's Slot. + * Accepts serializable props to avoid Flight lazy chunk wrappers reaching the trigger's `render` prop. */ export function HeaderLinkDropdown(props: { headerPreset: CustomizationHeaderPreset; @@ -59,6 +59,7 @@ export function HeaderLinkDropdown(props: { /> ) } + nativeButton={false} openOnHover={true} > {children} @@ -145,12 +146,12 @@ function getHeaderLinkClassName(_props: { headerPreset: CustomizationHeaderPrese 'text-tint', 'links-default:hover:text-primary', - 'links-default:data-[state=open]:text-primary', + 'links-default:data-popup-open:text-primary', 'links-default:tint:hover:text-tint-strong', - 'links-default:tint:data-[state=open]:text-tint-strong', + 'links-default:tint:data-popup-open:text-tint-strong', 'underline-offset-2', 'links-accent:hover:underline', - 'links-accent:data-[state=open]:underline', + 'links-accent:data-popup-open:underline', 'links-accent:underline-offset-4', 'links-accent:decoration-primary-subtle', 'links-accent:decoration-[3px]', diff --git a/packages/gitbook/src/components/Header/HeaderLinkMoreClient.tsx b/packages/gitbook/src/components/Header/HeaderLinkMoreClient.tsx index 7b453da4e..c8e9efa3d 100644 --- a/packages/gitbook/src/components/Header/HeaderLinkMoreClient.tsx +++ b/packages/gitbook/src/components/Header/HeaderLinkMoreClient.tsx @@ -10,7 +10,8 @@ import { type ClassValue, tcls } from '@/lib/tailwind'; /** * Client component for the "More" header link dropdown. - * Creates the trigger button internally to avoid Flight lazy chunk wrappers reaching Radix's Slot. + * Creates the trigger button internally to avoid Flight lazy chunk wrappers reaching the trigger's + * `render` prop. */ export function HeaderLinkMoreDropdown(props: { label: React.ReactNode; diff --git a/packages/gitbook/src/components/Header/SpacesDropdownClient.tsx b/packages/gitbook/src/components/Header/SpacesDropdownClient.tsx index 972049248..7e69844c1 100644 --- a/packages/gitbook/src/components/Header/SpacesDropdownClient.tsx +++ b/packages/gitbook/src/components/Header/SpacesDropdownClient.tsx @@ -8,8 +8,8 @@ import { SpacesDropdownMenuItems } from './SpacesDropdownMenuItem'; import { type ClassValue, tcls } from '@/lib/tailwind'; /** - * Client component that creates the Button trigger and DropdownMenu internally, - * avoiding server→client element serialization through Radix's Slot. + * Client component that creates the Button trigger and DropdownMenu internally, avoiding + * server→client element serialization through the trigger's `render` prop. */ export function SpacesDropdownClient(props: { title: string; diff --git a/packages/gitbook/src/components/PageBody/BreadcrumbItemDropdown.tsx b/packages/gitbook/src/components/PageBody/BreadcrumbItemDropdown.tsx index c611c7e7d..113ef132c 100644 --- a/packages/gitbook/src/components/PageBody/BreadcrumbItemDropdown.tsx +++ b/packages/gitbook/src/components/PageBody/BreadcrumbItemDropdown.tsx @@ -57,7 +57,7 @@ export function BreadcrumbItemDropdown(props: { // normalize "" to "/" the way the table of contents does. const trigger = href !== undefined ? ( - + {content} ) : ( @@ -75,6 +75,7 @@ export function BreadcrumbItemDropdown(props: { diff --git a/packages/gitbook/src/components/PageBody/PageHeader.tsx b/packages/gitbook/src/components/PageBody/PageHeader.tsx index 32f586c45..ebe6a3b1f 100644 --- a/packages/gitbook/src/components/PageBody/PageHeader.tsx +++ b/packages/gitbook/src/components/PageBody/PageHeader.tsx @@ -194,7 +194,7 @@ export async function PageHeader(props: { // overlaps it: mark it here so it is never briefly unstyled before hydration. data-cover-aware-text data-over-cover - className="page-cover-background:text-contrast-cover text-xs leading-relaxed text-tint layout-wide:page-no-toc:hidden page-cover-background:opacity-9" + className="page-cover-background:text-contrast-cover flow-root text-xs leading-relaxed text-tint layout-wide:page-no-toc:hidden page-cover-background:opacity-9" >
      {contextCrumbs.map((crumb, index) => ( diff --git a/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx b/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx index f231e2ee6..88957646d 100644 --- a/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx +++ b/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx @@ -1,6 +1,6 @@ 'use client'; -import { TooltipProvider } from '@radix-ui/react-tooltip'; +import { Tooltip } from '@base-ui/react/tooltip'; import type React from 'react'; import { NavigationStatusProvider, ScrollPage } from '../hooks'; @@ -19,12 +19,12 @@ export function RootLayoutClientContexts(props: { return ( - + {children} - + ); } diff --git a/packages/gitbook/src/components/RootLayout/globals.css b/packages/gitbook/src/components/RootLayout/globals.css index 6cb5d2ca2..1c542fd45 100644 --- a/packages/gitbook/src/components/RootLayout/globals.css +++ b/packages/gitbook/src/components/RootLayout/globals.css @@ -46,17 +46,6 @@ } } -/* - Radix renders tooltip content inside a portaled `[data-radix-popper-content-wrapper]` attached - to . A non-interactive tooltip (Tooltip with `disableHoverableContent`) marks its content - with `data-non-interactive`; without this rule the pointer-interactive wrapper could sit over - the trigger and steal its hover (e.g. breaking hover-revealed controls). We can't style the - wrapper from React, so let pointer events pass through it for these tooltips only. -*/ -[data-radix-popper-content-wrapper]:has([data-non-interactive]) { - pointer-events: none; -} - @utility linear-mask-gradient { mask-image: linear-gradient( to bottom, @@ -551,3 +540,15 @@ html.dark .highlight-line.diff-deleted .highlight-line-content::before { @apply pl-2; } } + +/* An open dropdown injects focus guards as siblings of its trigger, so `:last-child` is not the + last item of a button group: match the buttons and links themselves. */ +.button-group > :is(button, a):not(:only-child):first-child { + @apply rounded-r-none; +} +.button-group > :is(button, a):not(:first-child):not(:has(~ :is(button, a))) { + @apply rounded-l-none; +} +.button-group > :is(button, a):not(:first-child):has(~ :is(button, a)) { + @apply rounded-none!; +} diff --git a/packages/gitbook/src/components/Search/SearchContainer.tsx b/packages/gitbook/src/components/Search/SearchContainer.tsx index 39c08f5c5..7b05c9911 100644 --- a/packages/gitbook/src/components/Search/SearchContainer.tsx +++ b/packages/gitbook/src/components/Search/SearchContainer.tsx @@ -205,27 +205,27 @@ export function SearchContainer({ ) : ( event.preventDefault(), - align: 'start', - className: tcls( - '@container flex flex-col overflow-hidden bg-tint-base has-[.empty]:hidden w-128 p-0 max-w-[min(var(--radix-popover-content-available-width),32rem)]', - shouldFillHeight - ? 'h-[min(32rem,var(--radix-popover-content-available-height))]' - : 'max-h-[min(32rem,var(--radix-popover-content-available-height))]' - ), - onInteractOutside: (event) => { + onOpenChange: (nextOpen, eventDetails) => { + if (nextOpen) { + open(); + return; + } // Don't close if clicking on the search input itself - if (searchInputRef.current?.contains(event.target as Node)) { - event.preventDefault(); + if ( + eventDetails.reason === 'outside-press' && + searchInputRef.current?.contains(eventDetails.event.target as Node) + ) { + eventDetails.cancel(); return; } close(); }, + }} + positionerProps={{ + align: 'start', sideOffset: 8, collisionPadding: { top: 16, @@ -233,10 +233,18 @@ export function SearchContainer({ bottom: 32, left: 16, }, - hideWhenDetached: true, }} - triggerProps={{ - asChild: true, + popupProps={{ + initialFocus: false, + // The input is its own trigger: restoring focus to it would re-fire + // `onFocus` and reopen the popover. + finalFocus: false, + className: tcls( + '@container flex flex-col overflow-hidden bg-tint-base has-[.empty]:hidden w-128 p-0 max-w-[min(var(--available-width),32rem)]', + shouldFillHeight + ? 'h-[min(32rem,var(--available-height))]' + : 'max-h-[min(32rem,var(--available-height))]' + ), }} > diff --git a/packages/gitbook/src/components/SiteOAuthConsent/ConsentForm.tsx b/packages/gitbook/src/components/SiteOAuthConsent/ConsentForm.tsx index e01ece0f3..79f910cd9 100644 --- a/packages/gitbook/src/components/SiteOAuthConsent/ConsentForm.tsx +++ b/packages/gitbook/src/components/SiteOAuthConsent/ConsentForm.tsx @@ -62,7 +62,7 @@ export function ConsentForm(props: { setTrusted(value === true)} + onCheckedChange={setTrusted} /> I recognize and trust this client diff --git a/packages/gitbook/src/components/SiteSections/SiteSectionTabs.tsx b/packages/gitbook/src/components/SiteSections/SiteSectionTabs.tsx index d421c63f0..eca980535 100644 --- a/packages/gitbook/src/components/SiteSections/SiteSectionTabs.tsx +++ b/packages/gitbook/src/components/SiteSections/SiteSectionTabs.tsx @@ -1,11 +1,10 @@ 'use client'; -import * as NavigationMenu from '@radix-ui/react-navigation-menu'; +import { NavigationMenu } from '@base-ui/react/navigation-menu'; import React from 'react'; import type { IconName } from '@gitbook/icons'; -import { useIsMobile } from '../hooks/useIsMobile'; import { CONTAINER_STYLE } from '../layout'; import { ScrollContainer } from '../primitives/ScrollContainer'; import type { @@ -18,8 +17,10 @@ import { Button, Link, ToggleChevron } from '@/components/primitives'; import { tcls } from '@/lib/tailwind'; import { findSectionInGroup } from '@/lib/utils'; -const DESKTOP_BREAKPOINT = 768; const SCREEN_OFFSET = 16; // 1rem +const POPUP_OFFSET = 4; +const OPEN_DELAY_MS = 200; +const MOTION = 'duration-300 ease-[cubic-bezier(0.83,0,0.17,1)]'; const MAX_ITEMS_PER_COLUMN = 10; // number of items per column const GROUP_MASONRY_THRESHOLD = 3; // if a section group has more than this many child groups, it will be shown in a masonry grid const COLUMN_WIDTH = '18rem'; @@ -40,40 +41,25 @@ export function SiteSectionTabs(props: { children, } = props; - const containerRef = React.useRef(null); - const currentTriggerRef = React.useRef(null); - const [value, setValue] = React.useState(); - - const isMobile = useIsMobile(DESKTOP_BREAKPOINT); - const offset = useNavigationMenuViewportOffset({ - value, - isMobile, - triggerRef: currentTriggerRef, - containerRef, - }); - const viewportLeft = - !isMobile && offset !== null - ? `clamp(0px, calc(${offset - SCREEN_OFFSET}px - var(--radix-navigation-menu-viewport-width, 0px)/2), calc(100% - var(--radix-navigation-menu-viewport-width, 0px)))` - : '0px'; + // Portalled into the tabs container rather than , to keep inheriting the header's theming. + const containerRef = React.useRef(null); return structure.length > 0 ? ( 0 ? ( <> { - // Prevent clicking the trigger from closing when the viewport is open - if (value === id) { - e.preventDefault(); - e.stopPropagation(); - } - }} - > - - - -
      - -
      + } + /> + + ) : ( - - - + + } + /> )} ); @@ -164,70 +146,36 @@ export function SiteSectionTabs(props: { {children} -
      - + -
      + sideOffset={POPUP_OFFSET} + collisionPadding={SCREEN_OFFSET} + // Flipping above the tabs would hide the popup behind the header. + collisionAvoidance={{ side: 'none' }} + > + + + + +
      ) : null; } -function useNavigationMenuViewportOffset(args: { - value: string | undefined; - isMobile: boolean; - triggerRef: React.RefObject; - containerRef: React.RefObject; -}) { - const { value, isMobile, triggerRef, containerRef } = args; - const [offset, setOffset] = React.useState(null); - - React.useLayoutEffect(() => { - if (isMobile) { - setOffset(null); - return; - } - - if (!value) { - return; - } - - const trigger = triggerRef.current; - const container = containerRef.current; - if (!trigger || !container) { - return; - } - - const containerLeft = container.getBoundingClientRect().left; - const triggerWidth = trigger.getBoundingClientRect().width; - const triggerLeft = trigger.getBoundingClientRect().left - containerLeft; - - setOffset(triggerLeft + triggerWidth / 2); - }, [containerRef, isMobile, triggerRef, value]); - - return offset; -} - /** * A tab representing a section or section group */ diff --git a/packages/gitbook/src/components/primitives/Button.tsx b/packages/gitbook/src/components/primitives/Button.tsx index f78262469..ae2340a3d 100644 --- a/packages/gitbook/src/components/primitives/Button.tsx +++ b/packages/gitbook/src/components/primitives/Button.tsx @@ -212,15 +212,7 @@ export const Button = React.forwardRef< ); return (children || iconOnly) && label ? ( - + {button} ) : ( @@ -239,7 +231,13 @@ export const ButtonGroup = React.forwardRef< className={tcls( 'flex h-fit items-stretch justify-start overflow-hidden', combinedShape - ? '*:translate-y-0! *:shadow-none! [&>*:not(:first-child)]:border-l-0 [&>*:not(:first-child,:last-child)]:rounded-none! [&>*:not(:only-child):first-child]:rounded-r-none [&>*:not(:only-child):last-child]:rounded-l-none' + ? [ + // Per-segment rounding lives in `globals.css`: Tailwind cannot express + // "last button" once focus guards are injected. + 'button-group', + '*:translate-y-0! *:shadow-none!', + '[&>*:not(:first-child)]:border-l-0', + ] : '', className )} diff --git a/packages/gitbook/src/components/primitives/Checkbox.tsx b/packages/gitbook/src/components/primitives/Checkbox.tsx index 7dbe8b1a3..da37d2e15 100644 --- a/packages/gitbook/src/components/primitives/Checkbox.tsx +++ b/packages/gitbook/src/components/primitives/Checkbox.tsx @@ -1,13 +1,13 @@ 'use client'; -import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; -import React from 'react'; +import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox'; import { Icon, IconStyle } from '@gitbook/icons'; import { tcls } from '@/lib/tailwind'; -export type CheckboxProps = React.ComponentProps & { +export type CheckboxProps = Omit & { + className?: string; /** * The size of the checkbox. * @default medium @@ -15,36 +15,31 @@ export type CheckboxProps = React.ComponentProps size?: 'small' | 'medium'; }; -export const Checkbox = React.forwardRef< - React.ElementRef, - CheckboxProps ->(({ className, size = 'medium', ...props }, ref) => ( - - - {props.checked ? ( - - ) : null} - - -)); -Checkbox.displayName = CheckboxPrimitive.Root.displayName; +export function Checkbox({ className, size = 'medium', ...props }: CheckboxProps) { + return ( + + + + + + ); +} diff --git a/packages/gitbook/src/components/primitives/Collapsible.tsx b/packages/gitbook/src/components/primitives/Collapsible.tsx index 59915d359..3ecd01a9a 100644 --- a/packages/gitbook/src/components/primitives/Collapsible.tsx +++ b/packages/gitbook/src/components/primitives/Collapsible.tsx @@ -1,63 +1,48 @@ 'use client'; -import * as RadixCollapsible from '@radix-ui/react-collapsible'; -import { useEffect, useState } from 'react'; +import { Collapsible as BaseCollapsible } from '@base-ui/react/collapsible'; import { tcls } from '@/lib/tailwind'; export function Collapsible( - props: { + props: Omit & { children: React.ReactNode; - } & RadixCollapsible.CollapsibleProps + className?: string; + } ) { - const { children, open, defaultOpen, onOpenChange, className, ...rest } = props; - - useEffect(() => { - if (open !== undefined) { - setOpenState(open); - } - }, [open]); - - const [openState, setOpenState] = useState(open ?? defaultOpen ?? false); + const { children, className, ...rest } = props; return ( - { - setOpenState(nextOpen); - onOpenChange?.(nextOpen); - }} - > + {children} - + ); } export function CollapsibleTrigger( - props: { - children: React.ReactNode; - } & RadixCollapsible.CollapsibleTriggerProps + props: BaseCollapsible.Trigger.Props & { + children?: React.ReactNode; + } ) { - return {props.children}; + return ; } export function CollapsibleContent( - props: { + props: Omit & { children: React.ReactNode; - } & RadixCollapsible.CollapsibleContentProps + className?: string; + } ) { const { children, className, ...rest } = props; return ( - {children} - + ); } diff --git a/packages/gitbook/src/components/primitives/DropdownMenu.tsx b/packages/gitbook/src/components/primitives/DropdownMenu.tsx index 7dcc97b3a..c58e689d3 100644 --- a/packages/gitbook/src/components/primitives/DropdownMenu.tsx +++ b/packages/gitbook/src/components/primitives/DropdownMenu.tsx @@ -1,8 +1,8 @@ 'use client'; -import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu'; +import { Menu } from '@base-ui/react/menu'; import type { DetailedHTMLProps, HTMLAttributes } from 'react'; -import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { createContext, useCallback, useContext, useState } from 'react'; import { assert } from 'ts-essentials'; import { Icon, type IconName } from '@gitbook/icons'; @@ -25,31 +25,20 @@ const DropdownMenuContext = createContext<{ setOpen: () => {}, }); -const DROPDOWN_CONTENT_OUTER_CLASS = - 'z-50 flex max-h-(--radix-dropdown-menu-content-available-height) min-w-28 xs:min-w-40 max-w-(--radix-dropdown-menu-content-available-width) origin-top animate-scale-in flex-col pt-2 data-[state=closed]:animate-scale-out'; -const DROPDOWN_CONTENT_INNER_CLASS = - 'flex flex-col gap-1 overflow-auto circular-corners:rounded-xl rounded-md straight-corners:rounded-none border border-tint bg-tint-base p-2 shadow-lg'; +const DROPDOWN_POSITIONER_CLASS = + 'z-50 flex max-h-(--available-height) min-w-28 xs:min-w-40 max-w-(--available-width) flex-col pt-2 outline-hidden data-anchor-hidden:hidden'; +// The exit animation has to live on the popup: Base UI keeps it mounted until *its* animations end. +const DROPDOWN_POPUP_CLASS = + 'flex origin-(--transform-origin) animate-scale-in flex-col gap-1 overflow-auto circular-corners:rounded-xl rounded-md straight-corners:rounded-none border border-tint bg-tint-base p-2 shadow-lg outline-hidden data-closed:animate-scale-out'; -/** - * Grace period before a hover-opened menu closes, so the pointer can travel from the trigger to the - * (portalled) menu without it collapsing. Only used when `openDelay` is set. - */ const HOVER_CLOSE_DELAY_MS = 150; -/** Clear a pending timeout ref, if any. */ -function clearTimer(ref: { current: ReturnType | null }) { - if (ref.current) { - clearTimeout(ref.current); - ref.current = null; - } -} - /** * Button with a dropdown. */ export function DropdownMenu(props: { /** Content of the button */ - button: React.ReactNode; + button: React.ReactElement>; /** Tooltip label for the button */ buttonTooltip?: React.ReactNode; /** Content of the dropdown */ @@ -63,21 +52,22 @@ export function DropdownMenu(props: { * @default 0 */ openDelay?: number; + nativeButton?: boolean; /** * Side of the dropdown * @default "bottom" */ - side?: RadixDropdownMenu.DropdownMenuContentProps['side']; + side?: Menu.Positioner.Props['side']; /** * Alignment of the dropdown * @default "start" */ - align?: RadixDropdownMenu.DropdownMenuContentProps['align']; + align?: Menu.Positioner.Props['align']; /** * Distance between the trigger and the dropdown. * @default 0 */ - sideOffset?: RadixDropdownMenu.DropdownMenuContentProps['sideOffset']; + sideOffset?: Menu.Positioner.Props['sideOffset']; }) { const { button, @@ -86,101 +76,52 @@ export function DropdownMenu(props: { className, openOnHover = false, openDelay = 0, + nativeButton, side = 'bottom', align = 'start', sideOffset = 0, } = props; - const [hovered, setHovered] = useState(false); const [open, setOpen] = useState(false); - // Timers used to delay opening on hover (and to keep the menu open while the pointer travels - // between the trigger and the menu). - const openTimeout = useRef | null>(null); - const closeTimeout = useRef | null>(null); - // Clear any pending timers when unmounting. - useEffect( - () => () => { - clearTimer(openTimeout); - clearTimer(closeTimeout); - }, - [] - ); - - const handleHoverEnter = useCallback(() => { - clearTimer(closeTimeout); - if (openDelay > 0) { - if (!openTimeout.current) { - openTimeout.current = setTimeout(() => { - openTimeout.current = null; - setHovered(true); - }, openDelay); - } - } else { - setHovered(true); - } - }, [openDelay]); - - const handleHoverLeave = useCallback(() => { - clearTimer(openTimeout); - clearTimer(closeTimeout); - if (openDelay > 0) { - // Grace period so moving between the trigger and the menu doesn't close it. - closeTimeout.current = setTimeout(() => { - closeTimeout.current = null; - setHovered(false); - }, HOVER_CLOSE_DELAY_MS); - } else { - setHovered(false); - } - }, [openDelay]); - - const isOpen = openOnHover ? open || hovered : open; - const trigger = ( - (openOnHover ? setOpen(!open) : null)} + - {button} - + /> ); return ( - - + + {buttonTooltip ? ( - + {trigger} ) : ( trigger )} - - + -
      + {children} -
      -
      -
      -
      + + + +
      ); } @@ -211,7 +152,7 @@ export function DropdownMenuItem( children: React.ReactNode; leadingIcon?: IconName | React.ReactNode; } & LinkInsightsProps & - RadixDropdownMenu.DropdownMenuItemProps + Omit ) { const { children, @@ -247,20 +188,22 @@ export function DropdownMenuItem( if (href) { return ( - - - {icon} - {children} - - + } + > + {icon} + {children} + ); } return ( - + {icon} {children} - + ); } @@ -268,28 +211,24 @@ export function DropdownSubMenu(props: { children: React.ReactNode; label: React const { children, label } = props; return ( - - + + {label} - - - -
      {children}
      -
      -
      -
      + + + + {children} + + + ); } export function DropdownMenuSeparator(props: { className?: ClassValue }) { const { className } = props; return ( - ); diff --git a/packages/gitbook/src/components/primitives/HoverCard.tsx b/packages/gitbook/src/components/primitives/HoverCard.tsx index 596ea3be7..a6f9d2ffa 100644 --- a/packages/gitbook/src/components/primitives/HoverCard.tsx +++ b/packages/gitbook/src/components/primitives/HoverCard.tsx @@ -1,51 +1,64 @@ 'use client'; -import * as RadixHoverCard from '@radix-ui/react-hover-card'; +import { PreviewCard } from '@base-ui/react/preview-card'; +import { PopupArrow } from './PopupArrow'; import { tcls } from '@/lib/tailwind'; -export function HoverCardRoot(props: RadixHoverCard.HoverCardProps) { - return ( - - ); +export function HoverCardRoot(props: PreviewCard.Root.Props) { + return ; } -// We wrap the Trigger child in a span to avoid react slot seeing a lazy chunk -export function HoverCardTrigger(props: RadixHoverCard.HoverCardTriggerProps) { - const { children, ...rest } = props; +// Wrapped in a span so the render prop never receives a lazily-loaded component. +export function HoverCardTrigger(props: { + children: React.ReactNode; + className?: string; + openDelay?: number; + closeDelay?: number; +}) { + const { children, className, openDelay = 200, closeDelay = 100 } = props; return ( - - {children} - + } + delay={openDelay} + closeDelay={closeDelay} + > + {children} + ); } export function HoverCard( - props: RadixHoverCard.HoverCardContentProps & { arrow?: RadixHoverCard.HoverCardArrowProps } + props: Omit & { + className?: string; + side?: PreviewCard.Positioner.Props['side']; + arrow?: { className?: string }; + } ) { - const { arrow, ...cardProps } = props; + const { arrow, side = 'top', className, children, ...popupProps } = props; return ( - - + -
      - {cardProps.children} -
      - -
      -
      +
      + {children} +
      + + + + ); } diff --git a/packages/gitbook/src/components/primitives/Popover.tsx b/packages/gitbook/src/components/primitives/Popover.tsx index 503fd4c30..2a291de80 100644 --- a/packages/gitbook/src/components/primitives/Popover.tsx +++ b/packages/gitbook/src/components/primitives/Popover.tsx @@ -1,39 +1,40 @@ -import * as RadixPopover from '@radix-ui/react-popover'; +import { Popover as BasePopover } from '@base-ui/react/popover'; import { tcls } from '@/lib/tailwind'; export function Popover(props: { - children: React.ReactNode; + children: React.ReactElement>; content?: string | React.ReactNode; - rootProps?: RadixPopover.PopoverProps; - triggerProps?: RadixPopover.PopoverTriggerProps; - contentProps?: RadixPopover.PopoverContentProps; - arrow?: boolean; + rootProps?: Omit; + nativeButton?: boolean; + positionerProps?: Omit & { + className?: string; + }; + popupProps?: Omit & { className?: string }; }) { - const { children, content, rootProps, triggerProps, contentProps, arrow = false } = props; + const { children, content, rootProps, nativeButton, positionerProps, popupProps } = props; return ( - - {children} - - + + + - {content} - {arrow && ( - - )} - - - + + {content} + + + + ); } diff --git a/packages/gitbook/src/components/primitives/PopupArrow.tsx b/packages/gitbook/src/components/primitives/PopupArrow.tsx new file mode 100644 index 000000000..55d1cd1da --- /dev/null +++ b/packages/gitbook/src/components/primitives/PopupArrow.tsx @@ -0,0 +1,30 @@ +import { tcls } from '@/lib/tailwind'; + +// Base UI positions the arrow against the anchor but never rotates it, so it is drawn pointing up +// and turned per `data-side` here. +export function PopupArrow(props: { + arrow: React.ComponentType<{ className?: string; children?: React.ReactNode }>; + className?: string; +}) { + const { arrow: Arrow, className } = props; + + return ( + + + + ); +} diff --git a/packages/gitbook/src/components/primitives/ToggleChevron.tsx b/packages/gitbook/src/components/primitives/ToggleChevron.tsx index a5c3d64ce..5be8958b6 100644 --- a/packages/gitbook/src/components/primitives/ToggleChevron.tsx +++ b/packages/gitbook/src/components/primitives/ToggleChevron.tsx @@ -3,8 +3,8 @@ import { Icon, type IconName } from '@gitbook/icons'; import { type ClassValue, tcls } from '@/lib/tailwind'; /** - * Animated chevron to display in dropdowns and other toggleable elements. - * Uses the `data-[state=open]` attribute to animate the chevron, or you can pass it an `open` prop to control the state. + * Animated chevron to display in dropdowns and other toggleable elements. Picks up the open state + * from the enclosing `group/dropdown` or `
      `, unless an `open` prop is passed. */ export function ToggleChevron(props: { open?: boolean; @@ -21,12 +21,14 @@ export function ToggleChevron(props: { 'down-to-up': { icon: 'chevron-down', animation: 'rotate-180', - autoAnimation: 'group-data-[state=open]/dropdown:rotate-180 group-open:rotate-180', + autoAnimation: + 'group-data-popup-open/dropdown:rotate-180 group-data-panel-open/dropdown:rotate-180 group-open:rotate-180', }, 'right-to-down': { icon: 'chevron-right', animation: 'rotate-90', - autoAnimation: 'group-data-[state=open]/dropdown:rotate-90 group-open:rotate-90', + autoAnimation: + 'group-data-popup-open/dropdown:rotate-90 group-data-panel-open/dropdown:rotate-90 group-open:rotate-90', }, }; diff --git a/packages/gitbook/src/components/primitives/Tooltip.tsx b/packages/gitbook/src/components/primitives/Tooltip.tsx index 88a222fb1..0b8929c32 100644 --- a/packages/gitbook/src/components/primitives/Tooltip.tsx +++ b/packages/gitbook/src/components/primitives/Tooltip.tsx @@ -1,86 +1,96 @@ 'use client'; -import * as RadixTooltip from '@radix-ui/react-tooltip'; +import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'; import { useState } from 'react'; +import { PopupArrow } from './PopupArrow'; import { tcls } from '@/lib/tailwind'; export type TooltipProps = { - rootProps?: RadixTooltip.TooltipProps; - triggerProps?: RadixTooltip.TooltipTriggerProps; - contentProps?: RadixTooltip.TooltipContentProps; + side?: BaseTooltip.Positioner.Props['side']; + align?: BaseTooltip.Positioner.Props['align']; + sideOffset?: number; + delay?: number; + disabled?: boolean; + disableHoverablePopup?: boolean; + pinOnClick?: boolean; + popupProps?: Omit; }; -export function Tooltip(props: { - children: React.ReactNode; - label?: string | React.ReactNode; - triggerProps?: RadixTooltip.TooltipTriggerProps; - contentProps?: RadixTooltip.TooltipContentProps; - portalProps?: RadixTooltip.TooltipPortalProps; - rootProps?: RadixTooltip.TooltipProps; - arrowProps?: RadixTooltip.TooltipArrowProps; - arrow?: boolean; - className?: string; - pinOnClick?: boolean; -}) { +export function Tooltip( + props: TooltipProps & { + children: React.ReactElement>; + label?: string | React.ReactNode; + arrow?: boolean | { className?: string }; + className?: string; + } +) { const { children, label, - triggerProps, - contentProps, - portalProps, - rootProps, - arrowProps, + side, + align, + sideOffset = 4, + delay, + disabled, + disableHoverablePopup, + pinOnClick = false, + popupProps, arrow = false, className, - pinOnClick = true, } = props; - const [open, setOpen] = useState(false); - const [clicked, setClicked] = useState(false); - - // When hoverable content is disabled, the content is purely informational: make it - // non-interactive so its (portaled) popper wrapper can't steal hover/clicks from the - // trigger. The `data-non-interactive` marker lets a scoped global rule (globals.css) - // set `pointer-events: none` on the wrapper, which we can't reach from React. - const nonInteractive = rootProps?.disableHoverableContent ?? false; - const resolvedContentProps = nonInteractive - ? { - ...contentProps, - 'data-non-interactive': '', - style: { - ...contentProps?.style, - // Forced last so the non-interactive guarantee can't be overridden. - pointerEvents: 'none' as const, - userSelect: 'none' as const, - }, - } - : contentProps; + const [hovered, setHovered] = useState(false); + const [pinned, setPinned] = useState(false); return ( - - setClicked(true) : undefined} - {...triggerProps} - > - {children} - - - { + setHovered(nextOpen); + if ( + eventDetails.reason === 'outside-press' || + eventDetails.reason === 'escape-key' + ) { + setPinned(false); + } + }} + disabled={disabled} + disableHoverablePopup={disableHoverablePopup} + > + setPinned(true) : undefined} + /> + + setClicked(false)} - {...resolvedContentProps} > - {label} - {arrow && } - - - + + {label} + {arrow ? ( + + ) : null} + + + + ); } diff --git a/packages/gitbook/src/components/primitives/styles.ts b/packages/gitbook/src/components/primitives/styles.ts index c0f84705a..5c85178c7 100644 --- a/packages/gitbook/src/components/primitives/styles.ts +++ b/packages/gitbook/src/components/primitives/styles.ts @@ -33,7 +33,7 @@ export const ButtonStyles = [ 'hover:depth-subtle:-translate-y-px', 'focus-visible:depth-subtle:-translate-y-px', - 'data-[state=open]:depth-subtle:-translate-y-px', + 'data-popup-open:depth-subtle:-translate-y-px', 'active:depth-subtle:translate-y-0', 'transition-all', diff --git a/packages/react-openapi/package.json b/packages/react-openapi/package.json index 984b14195..80352447e 100644 --- a/packages/react-openapi/package.json +++ b/packages/react-openapi/package.json @@ -10,7 +10,7 @@ "version": "1.5.15", "sideEffects": false, "dependencies": { - "@base-ui/react": "^1.7.0", + "@base-ui/react": "catalog:", "@gitbook/expr": "workspace:*", "@gitbook/openapi-parser": "workspace:*", "@scalar/api-client-react": "catalog:", From 3e2968079215badc9fa8a6cc48fe807d87428608 Mon Sep 17 00:00:00 2001 From: Tomek Date: Mon, 17 Aug 2026 13:14:47 +0200 Subject: [PATCH 08/15] Stabilize visual CI tests (#4506) Co-authored-by: Claude Haiku 4.5 --- packages/gitbook/e2e/customers.spec.ts | 4 +++- packages/gitbook/e2e/internal.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/gitbook/e2e/customers.spec.ts b/packages/gitbook/e2e/customers.spec.ts index d464f4b1a..4c7f417b7 100644 --- a/packages/gitbook/e2e/customers.spec.ts +++ b/packages/gitbook/e2e/customers.spec.ts @@ -360,7 +360,9 @@ const testCases: TestsCase[] = [ { name: 'docs.verifone.com', contentBaseURL: 'https://docs.verifone.com', - tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }], + // Verifone's custom Cookiebot integration races with and suppresses GitBook's built-in + // banner, so waitForCookiesDialog is not a stable invariant here. + tests: [{ name: 'Home', url: '/' }], }, // Deactivate it because of a custom Ask AI that causes flakiness. // { diff --git a/packages/gitbook/e2e/internal.spec.ts b/packages/gitbook/e2e/internal.spec.ts index 1d85c094c..64cbffbdb 100644 --- a/packages/gitbook/e2e/internal.spec.ts +++ b/packages/gitbook/e2e/internal.spec.ts @@ -2413,7 +2413,7 @@ const testCases: TestsCase[] = [ actions.nth(1).click(), ]); // Verify the new page would have opened with the expected URL - expect(newPage.url()).toContain('gitbook.com'); + await expect(newPage).toHaveURL(/gitbook\.com/); // Close it immediately to avoid navigation await newPage.close(); @@ -2561,7 +2561,7 @@ const testCases: TestsCase[] = [ openInNewTabButton.click(), ]); // Verify the new page would have opened with the expected URL - expect(newPage.url()).toContain('gitbook.gitbook.io'); + await expect(newPage).toHaveURL(/gitbook\.gitbook\.io/); // Close it immediately to avoid navigation await newPage.close(); }, From 0f32eb17d188e767f4d37c1d740ced7298d76ce6 Mon Sep 17 00:00:00 2001 From: Claire Chabas Date: Mon, 17 Aug 2026 13:41:44 +0100 Subject: [PATCH 09/15] Fix toggle chevron alignment in ToC (#4505) --- .../TableOfContents/ToggleableLinkItem.tsx | 13 ++++--------- packages/gitbook/src/components/Tag.tsx | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx b/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx index 6c6ff96ae..93dd99b5b 100644 --- a/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx +++ b/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx @@ -50,7 +50,7 @@ export function ToggleableLinkItem( {tag ? (
      {children} -
      {tag}
      + {tag}
      ) : ( children @@ -73,17 +73,12 @@ export function ToggleableLinkItem( {tag ? (
      {children} -
      - {tag} - {toggler} -
      + {tag}
      ) : ( - <> - {children} - {toggler} - + children )} + {toggler} {descendants} diff --git a/packages/gitbook/src/components/Tag.tsx b/packages/gitbook/src/components/Tag.tsx index 92cdc91c6..5b7122c74 100644 --- a/packages/gitbook/src/components/Tag.tsx +++ b/packages/gitbook/src/components/Tag.tsx @@ -17,7 +17,7 @@ export function Tag(props: { tag: RevisionTag; className?: string }) { data-tag="" title={tag.label} className={tcls( - 'inline-flex items-center gap-1 rounded-full px-2 py-1 font-medium text-xs leading-normal contrast-more:ring-1 contrast-more:ring-tint', + 'inline-flex max-w-full shrink-0 items-center gap-1 rounded-full px-2 py-1 font-medium text-xs leading-normal contrast-more:ring-1 contrast-more:ring-tint', hasColor ? [backgroundColorToStyle[tag.color], textColorToStyle[tag.color]] : 'bg-tint-5 text-tint-strong', From 87fd234d551dd5bb1a05c42ff212360bb27e4989 Mon Sep 17 00:00:00 2001 From: Tomek Date: Mon, 17 Aug 2026 14:55:09 +0200 Subject: [PATCH 10/15] Fix dropdown icon rotation by reading dropdown open state (#4503) Co-authored-by: Claude Haiku 4.5 --- .../PageActions/PageActionsDropdown.tsx | 18 ++++++++++++++++-- .../src/components/primitives/DropdownMenu.tsx | 7 +++++++ .../components/primitives/ToggleChevron.tsx | 15 ++++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx b/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx index a18a1c4db..ec85fad98 100644 --- a/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx +++ b/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx @@ -24,8 +24,13 @@ import { type PageActionAssistantContext, } from './PageActions'; import { Button, ButtonGroup } from '@/components/primitives/Button'; -import { DropdownMenu, DropdownMenuSeparator } from '@/components/primitives/DropdownMenu'; +import { + DropdownMenu, + DropdownMenuSeparator, + useDropdownMenuOpen, +} from '@/components/primitives/DropdownMenu'; import { tString, useLanguage } from '@/intl/client'; +import type { ClassValue } from '@/lib/tailwind'; /** * Type of a built-in page action that can be displayed in the page actions menu. @@ -137,7 +142,7 @@ export function PageActionsDropdown(props: PageActionsDropdownProps) { className="!min-w-60 max-w-max" button={
    ); }); } +function AIChatAssistantGroup(props: { + chat: AIChatState; + chatController: AIChatController; + assistantItems: MessageGroup[]; + hasCommentary: boolean; + hasFinalAnswer: boolean; + toolCount: number; + firstAssistantIndex: number; + isLastMessage: boolean; + showLoadingShim: boolean; +}) { + const { + chat, + chatController, + assistantItems, + hasCommentary, + hasFinalAnswer, + toolCount, + firstAssistantIndex, + isLastMessage, + showLoadingShim, + } = props; + const language = useLanguage(); + // Base UI locks controlled/uncontrolled mode on first render, so once `hasFinalAnswer` + // flips the trigger over to being interactive, we need our own state to track manual toggles. + const [manuallyOpened, setManuallyOpened] = useState(false); + + return ( + + {hasCommentary && hasFinalAnswer ? ( + + } + > +
    + + {toolCount > 0 + ? t( + language, + 'ai_chat_explored_with', + tString( + language, + toolCount === 1 ? 'tool_count' : 'tool_count_plural', + toolCount.toString() + ) + ) + : t(language, 'ai_chat_explored')} + + +
    +
    + ) : null} + + {assistantItems.map(({ message, originalIndex }) => ( + {message.content} + ))} + + {isLastMessage ? ( +
    + message.content) ? 'hidden' : '' + } + /> + +
    + ) : null} + + {isLastMessage ? ( + <> + {!chat.responding && + !chat.error && + chat.query && + chat.responseId && + !chat.control ? ( + + ) : null} + + + ) : null} +
    + ); +} + export function HoldMessage({ breakLines = false, className, diff --git a/packages/gitbook/src/components/Header/HeaderLinkDropdown.tsx b/packages/gitbook/src/components/Header/HeaderLinkDropdown.tsx index 573413937..b9112a060 100644 --- a/packages/gitbook/src/components/Header/HeaderLinkDropdown.tsx +++ b/packages/gitbook/src/components/Header/HeaderLinkDropdown.tsx @@ -146,12 +146,12 @@ function getHeaderLinkClassName(_props: { headerPreset: CustomizationHeaderPrese 'text-tint', 'links-default:hover:text-primary', - 'links-default:data-popup-open:text-primary', + 'links-default:aria-expanded:text-primary', 'links-default:tint:hover:text-tint-strong', - 'links-default:tint:data-popup-open:text-tint-strong', + 'links-default:tint:aria-expanded:text-tint-strong', 'underline-offset-2', 'links-accent:hover:underline', - 'links-accent:data-popup-open:underline', + 'links-accent:aria-expanded:underline', 'links-accent:underline-offset-4', 'links-accent:decoration-primary-subtle', 'links-accent:decoration-[3px]', diff --git a/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx b/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx index ec85fad98..a18a1c4db 100644 --- a/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx +++ b/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx @@ -24,13 +24,8 @@ import { type PageActionAssistantContext, } from './PageActions'; import { Button, ButtonGroup } from '@/components/primitives/Button'; -import { - DropdownMenu, - DropdownMenuSeparator, - useDropdownMenuOpen, -} from '@/components/primitives/DropdownMenu'; +import { DropdownMenu, DropdownMenuSeparator } from '@/components/primitives/DropdownMenu'; import { tString, useLanguage } from '@/intl/client'; -import type { ClassValue } from '@/lib/tailwind'; /** * Type of a built-in page action that can be displayed in the page actions menu. @@ -142,7 +137,7 @@ export function PageActionsDropdown(props: PageActionsDropdownProps) { className="!min-w-60 max-w-max" button={