Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot] 84fbd82140 Version Packages 2026-10-29 15:44:43 +00:00
34 changed files with 99 additions and 367 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Bump Next.js to 16.4.3, keeping the dev-mode OOM patch.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Preconnect each asset origin once, skip same-origin icon URLs, and open the fonts preconnect in CORS mode so font requests can reuse it.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Forward the visitor's user agent on ask and Assistant calls so questions are classified by who asked.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Load the inline expression runtime only on pages whose code blocks or text use expressions, so other pages ship less JavaScript.
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/react-contentkit": patch
---
Load a webframe only once it is near the viewport. A page can hold hundreds of them inside collapsed expandables, and loading every one on mount held up the first paint.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Pack the class lists that every block, heading anchor and table cell repeats into shared CSS classes, and take each column's width from the table rather than from a style attribute on every cell, so a large page carries far less markup. Off-screen expandables also skip their rendering work.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Split llms-full.txt into parts by size so large sites no longer fail with "content too large"
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Follow site and space redirects when the MCP getPage tool resolves a page URL.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Add a search input to filter models by name on OpenAPI models pages.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Let analytics tools like GA4 track outbound link clicks by using native link navigation.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Set `Partitioned` on visitor cookies set inside an iframe so authenticated embeds work cross-site.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Support a secondary brand colour for secondary buttons and `$secondary` text, background, icon and tag colours. Sites without one render as before.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Resolve the published content with the full URL in a single lookup and always send the visitor country.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Keep lists, paragraphs and code blocks as HTML in Updates RSS feeds, instead of running all the text together
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Only apply a site preview customization override to the section or variant it was made for.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Render soft line breaks in paragraphs that contain buttons.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Truncate long tab titles one by one and show the selected tab's full title, instead of hiding tabs behind a "…" menu
+1 -1
View File
@@ -110,7 +110,7 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.28.0",
"version": "0.28.1",
"dependencies": {
"@base-ui/react": "catalog:",
"@cloudflare/workers-types": "^4.20251011.0",
+20
View File
@@ -1,5 +1,25 @@
# gitbook
## 0.28.1
### Patch Changes
- 7e096d5: Bump Next.js to 16.4.3, keeping the dev-mode OOM patch.
- f61578f: Preconnect each asset origin once, skip same-origin icon URLs, and open the fonts preconnect in CORS mode so font requests can reuse it.
- a512777: Forward the visitor's user agent on ask and Assistant calls so questions are classified by who asked.
- 508f1bc: Load the inline expression runtime only on pages whose code blocks or text use expressions, so other pages ship less JavaScript.
- bd431f1: Split llms-full.txt into parts by size so large sites no longer fail with "content too large"
- a5d58bc: Follow site and space redirects when the MCP getPage tool resolves a page URL.
- e745c88: Add a search input to filter models by name on OpenAPI models pages.
- 96c5683: Let analytics tools like GA4 track outbound link clicks by using native link navigation.
- 347a1cc: Set `Partitioned` on visitor cookies set inside an iframe so authenticated embeds work cross-site.
- ea01850: Support a secondary brand colour for secondary buttons and `$secondary` text, background, icon and tag colours. Sites without one render as before.
- c00a7b3: Resolve the published content with the full URL in a single lookup and always send the visitor country.
- b7b8ae6: Keep lists, paragraphs and code blocks as HTML in Updates RSS feeds, instead of running all the text together
- 4a72ead: Only apply a site preview customization override to the section or variant it was made for.
- 38f60f2: Render soft line breaks in paragraphs that contain buttons.
- be658e1: Truncate long tab titles one by one and show the selected tab's full title, instead of hiding tabs behind a "…" menu
## 0.28.0
### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "0.28.0",
"version": "0.28.1",
"private": true,
"dependencies": {
"@base-ui/react": "catalog:",
@@ -1,13 +0,0 @@
@reference "../RootLayout/globals.css";
/* Every block on every page carries these, so they live here rather than in each class attribute. */
@layer components {
.gb-block {
@apply decoration-primary/6;
}
/* Lists opt out: a long list is expected to break across printed pages. */
.gb-block:not(.gb-block-list) {
@apply print:break-inside-avoid;
}
}
@@ -2,7 +2,6 @@ import type { DocumentBlock, JSONDocument } from '@gitbook/api';
import { CONTENT_STYLE, CONTENT_STYLE_REDUCED } from '../layout';
import { Block } from './Block';
import './Blocks.css';
import type { DocumentContextProps } from './DocumentView';
import { isBlockOffscreen } from './utils';
import { type ClassValue, tcls } from '@/lib/tailwind';
@@ -94,10 +93,10 @@ export function UnwrappedBlocks<TBlock extends DocumentBlock>(props: UnwrappedBl
key={node.key || `${node.type}-${index}`}
block={node}
style={[
'gb-block',
'decoration-primary/6',
FULL_WIDTH_BLOCKS.has(node.type) ? CONTENT_STYLE : CONTENT_STYLE_REDUCED,
LIST_BLOCKS.has(node.type) && 'gb-block-list',
!LIST_BLOCKS.has(node.type) && 'print:break-inside-avoid',
blockStyle,
]}
isEstimatedOffscreen={isOffscreen}
@@ -2,7 +2,6 @@
import React from 'react';
import './style.css';
import { useHash } from '@/components/hooks';
import { type ClassValue, tcls } from '@/lib/tailwind';
@@ -54,7 +53,6 @@ export function Details(props: {
className={tcls(
className,
'group/expandable',
'expandable-block',
'bg-tint-base',
'border',
'border-b-0',
@@ -1,28 +0,0 @@
@reference "../../RootLayout/globals.css";
/* A page can hold a hundred expandables, each hiding a table; skipping the render work for the
off-screen ones is what keeps opening one cheap. The DOM is untouched, so the server HTML,
find-in-page and #anchors all still work. */
.expandable-block {
/* Below Safari 18 / Firefox 125 this is ignored and every expandable renders, as it does today. */
/* stylelint-disable-next-line plugin/no-unsupported-browser-features */
content-visibility: auto;
/* Only the block axis: the inline size comes from the parent. Plain length first, as a fallback
for engines without the `auto` keyword, where a skipped block would collapse to 0. The length
is a closed expandable with a one-line summary — `auto` swaps in the real size once rendered. */
contain-intrinsic-height: 58px;
contain-intrinsic-height: auto 58px;
}
/* PDF export lays the whole space out off-screen, so nothing there may be skipped. */
body:has(.print-mode) .expandable-block {
/* stylelint-disable-next-line plugin/no-unsupported-browser-features */
content-visibility: visible;
}
@media print {
.expandable-block {
/* stylelint-disable-next-line plugin/no-unsupported-browser-features */
content-visibility: visible;
}
}
@@ -1,25 +0,0 @@
@reference "../RootLayout/globals.css";
/* A page carries one of these per heading, so the shared utilities live here instead of in a class
attribute repeated hundreds of times — on a large page that repetition is most of the HTML.
They sit in `components` so the utilities passed as props still override them. */
@layer components {
.hash {
@apply relative inline-grid grid-area-1-1 border-0 rounded site-background;
@apply pointer-events-none opacity-0 transition-opacity;
@apply pointer-fine:h-[1em] pointer-fine:pointer-events-auto;
@apply pointer-fine:group-hover/hash:opacity-100 pointer-fine:group-focus-within/hash:opacity-100;
@apply [.hash-revealed_&]:pointer-events-auto [.hash-revealed_&]:opacity-100;
}
.hash-link {
@apply inline-flex items-center p-1 pointer-fine:h-full pointer-fine:p-0;
}
.hash-icon {
@apply size-3 self-center text-transparent transition-colors;
@apply pointer-fine:group-hover/hash:text-tint-subtle contrast-more:pointer-fine:group-hover/hash:text-tint-strong;
@apply pointer-fine:group-focus-within/hash:text-tint-subtle contrast-more:pointer-fine:group-focus-within/hash:text-tint-strong;
@apply [.hash-revealed_&]:text-tint-subtle contrast-more:[.hash-revealed_&]:text-tint-strong;
}
}
@@ -3,7 +3,6 @@ import { Icon } from '@gitbook/icons';
import { Link } from '../primitives';
import type { DocumentContext } from './DocumentView';
import './HashLinkButton.css';
import { getBlockTextStyle } from './spacing';
import { type ClassValue, tcls } from '@/lib/tailwind';
@@ -36,13 +35,53 @@ export function HashLinkButton(props: {
const textStyle = getBlockTextStyle(block);
return (
<span className={tcls('hash', className)}>
<span
className={tcls(
'relative',
'hash',
'inline-grid',
'grid-area-1-1',
'pointer-fine:h-[1em]',
'border-0',
'opacity-0',
'pointer-events-none',
'site-background',
'rounded',
'transition-opacity',
'pointer-fine:pointer-events-auto',
'pointer-fine:group-hover/hash:opacity-100',
'pointer-fine:group-focus-within/hash:opacity-100',
'[.hash-revealed_&]:opacity-100',
'[.hash-revealed_&]:pointer-events-auto',
className
)}
>
<Link
href={`#${id}`}
aria-label={label}
className={tcls('hash-link', textStyle.lineHeight)}
className={tcls(
'inline-flex items-center',
'p-1',
'pointer-fine:h-full pointer-fine:p-0',
textStyle.lineHeight
)}
>
<Icon icon="hashtag" className={tcls('hash-icon', iconClassName)} />
<Icon
icon="hashtag"
className={tcls(
'size-3',
'self-center',
'transition-colors',
'text-transparent',
'pointer-fine:group-hover/hash:text-tint-subtle',
'contrast-more:pointer-fine:group-hover/hash:text-tint-strong',
'pointer-fine:group-focus-within/hash:text-tint-subtle',
'contrast-more:pointer-fine:group-focus-within/hash:text-tint-strong',
'[.hash-revealed_&]:text-tint-subtle',
'contrast-more:[.hash-revealed_&]:text-tint-strong',
iconClassName
)}
/>
</Link>
</span>
);
@@ -1,11 +1,9 @@
import { getCommentCellProps } from '../utils';
import { getMergedCellWidth, getTableCellMerge } from './cellMerges';
import './columnWidths.css';
import { MAX_COLUMN_WIDTH_VARS, getColumnWidth } from './layout';
import { getColumnWidth } from './layout';
import { RecordColumnValue } from './RecordColumnValue';
import type { TableGridViewProps, TableRecordKV } from './Table';
import { TableSearchRecord } from './TableSearch';
import './styles.css';
import { getColumnVerticalAlignment } from './utils';
import { tcls } from '@/lib/tailwind';
@@ -25,32 +23,30 @@ export function RecordRow(
role="row"
recordId={record[0]}
visibleClassName="flex"
className={tcls('group/row', 'table-grid-row')}
className={tcls(
'group/row',
'border-tint-subtle',
'transition-colors',
'hover:bg-tint-hover'
)}
>
{view.columns.map((column, columnIndex) => {
{view.columns.map((column) => {
const cellMerge = getTableCellMerge(cellMergeLayout, record[0], column);
if (cellMerge && !cellMerge.isAnchor) {
return null;
}
const renderedColumns = cellMerge?.merge.columns ?? [column];
// A plain cell takes its width from the column variable the grid sets; only merged
// cells and columns past the generated classes need a width of their own.
const widthFromColumnVar =
renderedColumns.length === 1 && columnIndex < MAX_COLUMN_WIDTH_VARS;
const columnWidth = widthFromColumnVar
? undefined
: getMergedCellWidth(
renderedColumns.map((mergedColumn) =>
getColumnWidth({
column: mergedColumn,
columnWidths:
context.mode === 'print' ? undefined : view.columnWidths,
autoSizedColumns,
fixedColumns,
})
)
);
const columnWidth = getMergedCellWidth(
renderedColumns.map((mergedColumn) =>
getColumnWidth({
column: mergedColumn,
columnWidths: context.mode === 'print' ? undefined : view.columnWidths,
autoSizedColumns,
fixedColumns,
})
)
);
const isStickyFirstColumnCell = stickyFirstColumn && column === firstVisibleColumn;
// @ts-expect-error
const verticalAlignment = getColumnVerticalAlignment(block.data.definition[column]);
@@ -62,15 +58,16 @@ export function RecordRow(
{...getCommentCellProps(context, record[0], column)}
aria-colspan={cellMerge?.merge.colSpan}
className={tcls(
'table-grid-cell',
widthFromColumnVar ? `gb-tcol-${columnIndex}` : undefined,
isStickyFirstColumnCell ? 'table-grid-cell-sticky' : undefined
'relative flex flex-1 border-r px-3 py-2 align-middle text-sm last:border-r-0',
'border-tint-subtle',
isStickyFirstColumnCell
? 'sticky left-0 z-10 bg-tint-base group-hover/row:bg-tint-hover'
: undefined
)}
style={
widthFromColumnVar
? undefined
: { width: columnWidth, minWidth: columnWidth || '100px' }
}
style={{
width: columnWidth,
minWidth: columnWidth || '100px',
}}
>
<RecordColumnValue
{...props}
@@ -1,4 +1,4 @@
import { getColumnWidth, getColumnWidthVars, getViewGridLayout } from './layout';
import { getColumnWidth, getViewGridLayout } from './layout';
import { RecordRow } from './RecordRow';
import type { TableGridViewProps } from './Table';
import { getColumnAlignment } from './utils';
@@ -82,7 +82,7 @@ export function ViewGridHeader(props: ViewGridHeaderProps) {
export function ViewGrid(props: ViewGridProps) {
const { block, view, records, context, tableClassName } = props;
const { tableWidth, columnWidths, autoSizedColumns, fixedColumns } = getViewGridLayout({
const { tableWidth, autoSizedColumns, fixedColumns } = getViewGridLayout({
block,
view,
mode: context.mode,
@@ -92,12 +92,6 @@ export function ViewGrid(props: ViewGridProps) {
<div
role="rowgroup"
className={tcls('flex', 'flex-col', tableClassName ?? tableWidth, '[&>*+*]:border-t')}
style={getColumnWidthVars({
columns: view.columns,
columnWidths,
autoSizedColumns,
fixedColumns,
})}
>
{records.map((record) => (
<RecordRow
@@ -1,100 +0,0 @@
/* Column widths are a property of the column, not of the cell, yet a grid repeats them on every
cell it renders. The table sets one custom property per column and each cell points at it with
one of these classes, which keeps the width out of a per-cell style attribute.
Columns past this many, and cells merged across columns, fall back to an inline width. */
.gb-tcol-0 {
width: var(--gb-tcol-0);
min-width: var(--gb-tcol-0);
}
.gb-tcol-1 {
width: var(--gb-tcol-1);
min-width: var(--gb-tcol-1);
}
.gb-tcol-2 {
width: var(--gb-tcol-2);
min-width: var(--gb-tcol-2);
}
.gb-tcol-3 {
width: var(--gb-tcol-3);
min-width: var(--gb-tcol-3);
}
.gb-tcol-4 {
width: var(--gb-tcol-4);
min-width: var(--gb-tcol-4);
}
.gb-tcol-5 {
width: var(--gb-tcol-5);
min-width: var(--gb-tcol-5);
}
.gb-tcol-6 {
width: var(--gb-tcol-6);
min-width: var(--gb-tcol-6);
}
.gb-tcol-7 {
width: var(--gb-tcol-7);
min-width: var(--gb-tcol-7);
}
.gb-tcol-8 {
width: var(--gb-tcol-8);
min-width: var(--gb-tcol-8);
}
.gb-tcol-9 {
width: var(--gb-tcol-9);
min-width: var(--gb-tcol-9);
}
.gb-tcol-10 {
width: var(--gb-tcol-10);
min-width: var(--gb-tcol-10);
}
.gb-tcol-11 {
width: var(--gb-tcol-11);
min-width: var(--gb-tcol-11);
}
.gb-tcol-12 {
width: var(--gb-tcol-12);
min-width: var(--gb-tcol-12);
}
.gb-tcol-13 {
width: var(--gb-tcol-13);
min-width: var(--gb-tcol-13);
}
.gb-tcol-14 {
width: var(--gb-tcol-14);
min-width: var(--gb-tcol-14);
}
.gb-tcol-15 {
width: var(--gb-tcol-15);
min-width: var(--gb-tcol-15);
}
.gb-tcol-16 {
width: var(--gb-tcol-16);
min-width: var(--gb-tcol-16);
}
.gb-tcol-17 {
width: var(--gb-tcol-17);
min-width: var(--gb-tcol-17);
}
.gb-tcol-18 {
width: var(--gb-tcol-18);
min-width: var(--gb-tcol-18);
}
.gb-tcol-19 {
width: var(--gb-tcol-19);
min-width: var(--gb-tcol-19);
}
.gb-tcol-20 {
width: var(--gb-tcol-20);
min-width: var(--gb-tcol-20);
}
.gb-tcol-21 {
width: var(--gb-tcol-21);
min-width: var(--gb-tcol-21);
}
.gb-tcol-22 {
width: var(--gb-tcol-22);
min-width: var(--gb-tcol-22);
}
.gb-tcol-23 {
width: var(--gb-tcol-23);
min-width: var(--gb-tcol-23);
}
@@ -39,27 +39,6 @@ export function getViewGridLayout({
};
}
/** How many columns `columnWidths.css` carries a `.gb-tcol-*` class for. */
export const MAX_COLUMN_WIDTH_VARS = 24;
/**
* Custom properties holding each column's width, set once on the grid so the cells can point at
* them instead of repeating the width in a style attribute of their own.
*/
export function getColumnWidthVars(args: {
columns: string[];
columnWidths: Record<string, number> | undefined;
autoSizedColumns: string[];
fixedColumns: string[];
}): Record<string, string> {
const { columns, ...widthArgs } = args;
const vars: Record<string, string> = {};
columns.slice(0, MAX_COLUMN_WIDTH_VARS).forEach((column, index) => {
vars[`--gb-tcol-${index}`] = getColumnWidth({ column, ...widthArgs });
});
return vars;
}
export const getColumnWidth = ({
column,
columnWidths,
@@ -1,19 +0,0 @@
@reference "../../RootLayout/globals.css";
/* A grid view repeats these on every row and every cell, so a table of any size pays for the class
lists many times over in the HTML. They sit in `components` so the utilities passed alongside
them still override. */
@layer components {
.table-grid-row {
@apply border-tint-subtle transition-colors hover:bg-tint-hover;
}
.table-grid-cell {
@apply relative flex flex-1 border-r border-tint-subtle px-3 py-2 align-middle text-sm;
@apply last:border-r-0;
}
.table-grid-cell-sticky {
@apply sticky left-0 z-10 bg-tint-base group-hover/row:bg-tint-hover;
}
}
@@ -1,18 +0,0 @@
@reference "../../RootLayout/globals.css";
/* Blocks repeat their alignment on every paragraph, heading and cell, so the three utilities each
alignment needs are packed into one class. They sit in `components`, so a utility passed
alongside one still wins. */
@layer components {
.gb-align-start {
@apply justify-start self-start text-start;
}
.gb-align-center {
@apply justify-center self-center text-center;
}
.gb-align-end {
@apply justify-end self-end text-end;
}
}
@@ -1,22 +1,20 @@
import { TextAlignment } from '@gitbook/api';
import './textAlignment.css';
import type { ClassValue } from '@/lib/tailwind';
import { nullIfNever } from '@/lib/typescript';
/**
* Get the tailwind class for a text alignment.
* Each alignment packs three utilities, so it is a single class from `textAlignment.css`.
*/
export function getTextAlignment(textAlignment: TextAlignment | undefined): ClassValue {
switch (textAlignment) {
case undefined:
case TextAlignment.Start:
return 'gb-align-start';
return ['text-start', 'self-start', 'justify-start'];
case TextAlignment.Center:
return 'gb-align-center';
return ['text-center', 'self-center', 'justify-center'];
case TextAlignment.End:
return 'gb-align-end';
return ['text-end', 'self-end', 'justify-end'];
default:
return nullIfNever(textAlignment);
}
@@ -196,10 +196,6 @@ export function ElementWebframe(props: ContentKitClientElementProps<ContentKitWe
// Rendered without a source before mount so it reserves its size in the SSR layout.
src={mounted ? element.source.url : undefined}
title={element.source.url}
// A page can hold hundreds of these, most of them inside collapsed expandables.
// Loading them all on mount delays the first paint by seconds, so each one waits
// until it is near the viewport.
loading="lazy"
allowFullScreen
allow="clipboard-write"
className="contentkit-webframe block h-full w-full max-w-full border-none"