From 2de316b802d499f301616809072261d987d6418d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 22:34:41 +0000 Subject: [PATCH 01/15] docs(website): Restyle playground chrome and diff highlighting - Editor tab bar: selected tab joins the code panel with a primary top edge; light mode bar matches the Live Preview header - EndpointPlayground: one dark header style, "Usage" label for the calling code, status pill (e.g. "200 OK") - Tighter Store toggle letters; base styles for preview buttons and inputs - DiffEditor: highlight only changed characters; slim Before/After labels aligned with each pane Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- docs/rest/api/Invalidate.md | 4 +- docs/rest/api/resource.md | 18 +- docs/rest/guides/auth.md | 8 +- docs/rest/guides/axios-migration.md | 6 +- docs/rest/guides/django.md | 2 +- website/src/components/DiffEditorMonaco.tsx | 45 ++++- website/src/components/HTTP/Response.tsx | 22 ++- .../src/components/HTTP/Wrapper.module.css | 17 +- .../Playground/editor/EditorSurface.tsx | 2 +- .../src/components/Playground/monaco/theme.ts | 8 + .../components/Playground/styles.module.css | 156 ++++++++++++++---- 11 files changed, 233 insertions(+), 55 deletions(-) diff --git a/docs/rest/api/Invalidate.md b/docs/rest/api/Invalidate.md index b1016bc9f151..9e7c20e8984b 100644 --- a/docs/rest/api/Invalidate.md +++ b/docs/rest/api/Invalidate.md @@ -140,7 +140,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.deleteMany(['5', '13', '7']); ``` @@ -192,7 +192,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.deleteMany(['5', '13', '7']); ``` diff --git a/docs/rest/api/resource.md b/docs/rest/api/resource.md index f22393226461..4beed768a7b6 100644 --- a/docs/rest/api/resource.md +++ b/docs/rest/api/resource.md @@ -285,7 +285,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.get({ group: 'react', @@ -327,7 +327,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.getList({ group: 'react', @@ -380,7 +380,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.getList.push( { group: 'react', author: 'clara' }, @@ -424,7 +424,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.getList.unshift( { group: 'react', author: 'clara' }, @@ -471,7 +471,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.getList.getPage({ group: 'react', @@ -519,7 +519,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.getList.move( { group: 'react', id: '1' }, @@ -562,7 +562,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.update( { group: 'react', id: '1' }, @@ -605,7 +605,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.partialUpdate( { group: 'react', id: '1' }, @@ -648,7 +648,7 @@ export const PostResource = resource({ }); ``` -```typescript title="Request" column +```typescript title="Usage" column import { PostResource } from './Resource'; PostResource.delete({ group: 'react', id: '1' }); ``` diff --git a/docs/rest/guides/auth.md b/docs/rest/guides/auth.md index 493e034f4440..a2264e473472 100644 --- a/docs/rest/guides/auth.md +++ b/docs/rest/guides/auth.md @@ -47,7 +47,7 @@ export const MyResource = resource({ }); ``` -```ts title="Request" column +```ts title="Usage" column import { MyResource } from './MyResource'; MyResource.get({ id: 1 }); ``` @@ -128,7 +128,7 @@ export const MyResource = resource({ }); ``` -```ts title="Request" column +```ts title="Usage" column import { MyResource } from './MyResource'; MyResource.get({ id: 1 }); ``` @@ -204,7 +204,7 @@ export const MyResource = resource({ }); ``` -```ts title="Request" column +```ts title="Usage" column import { MyResource } from './MyResource'; MyResource.get({ id: 1 }); ``` @@ -279,7 +279,7 @@ export const MyResource = resource({ }); ``` -```ts title="Request" column +```ts title="Usage" column import { MyResource } from './MyResource'; MyResource.get({ id: 1 }); ``` diff --git a/docs/rest/guides/axios-migration.md b/docs/rest/guides/axios-migration.md index 37c82ff86665..0969e98ef9ad 100644 --- a/docs/rest/guides/axios-migration.md +++ b/docs/rest/guides/axios-migration.md @@ -115,7 +115,7 @@ export const getUser = new RestEndpoint({ }); ``` -```ts title="Request" column +```ts title="Usage" column import { getUser } from './api'; getUser({ id: '1' }); ``` @@ -190,7 +190,7 @@ export const PostResource = resource({ }); ``` -```ts title="Request" column +```ts title="Usage" column import { PostResource } from './PostResource'; PostResource.get({ id: '1' }); ``` @@ -244,7 +244,7 @@ export const PostResource = resource({ }); ``` -```ts title="Request" column +```ts title="Usage" column import { PostResource } from './PostResource'; PostResource.getList.push({ title: 'New Post', diff --git a/docs/rest/guides/django.md b/docs/rest/guides/django.md index e4b422866a0e..8f55c723c42c 100644 --- a/docs/rest/guides/django.md +++ b/docs/rest/guides/django.md @@ -75,7 +75,7 @@ export const MyResource = resource({ }); ``` -```ts title="Request" column +```ts title="Usage" column import { MyResource } from './MyResource'; MyResource.get({ id: 1 }); ``` diff --git a/website/src/components/DiffEditorMonaco.tsx b/website/src/components/DiffEditorMonaco.tsx index a88cf26abc91..c31e19121227 100644 --- a/website/src/components/DiffEditorMonaco.tsx +++ b/website/src/components/DiffEditorMonaco.tsx @@ -1,8 +1,11 @@ import BrowserOnly from '@docusaurus/BrowserOnly'; -import { DiffEditor as BaseDiffEditor } from '@monaco-editor/react'; +import { + DiffEditor as BaseDiffEditor, + type MonacoDiffEditor, +} from '@monaco-editor/react'; import clsx from 'clsx'; import { type editor } from 'monaco-editor'; -import { useMemo } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import type { CodeDocument } from './Playground/editor/codeModel'; import { extensionToMonacoLanguage } from './Playground/monaco/language'; @@ -25,6 +28,30 @@ export default function DiffEditor({ documents, fallback }: DiffMonacoProps) { options.lineHeight, }); + // Left edge of each pane's code, so labels line up with the first column + const [labelOffsets, setLabelOffsets] = useState(); + const handleDiffMount = useCallback( + (editor: MonacoDiffEditor) => { + handleMount(editor); + const panes = [editor.getOriginalEditor(), editor.getModifiedEditor()]; + const updateOffsets = () => { + const left = editor.getContainerDomNode().getBoundingClientRect().left; + setLabelOffsets( + panes.map( + pane => + (pane.getDomNode()?.getBoundingClientRect().left ?? left) - + left + + pane.getLayoutInfo().contentLeft, + ), + ); + }; + updateOffsets(); + // disposed along with the panes + panes.forEach(pane => pane.onDidLayoutChange(updateOffsets)); + }, + [handleMount], + ); + return ( {() => { @@ -41,13 +68,25 @@ export default function DiffEditor({ documents, fallback }: DiffMonacoProps) { )} >
+
+ {documents.map(({ title }, i) => ( + + {title || (i ? 'After' : 'Before')} + + ))} +
Response - {status} + = 400, + })} + > + {`${status} ${STATUS_TEXT[status] ?? ''}`.trim()} +
{response ? JSON.stringify(response, undefined, 2) : 'NO CONTENT'} @@ -21,3 +27,17 @@ interface Props { response: JSON; status: number; } + +const STATUS_TEXT: Record = { + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 204: 'No Content', + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Not Found', + 409: 'Conflict', + 422: 'Unprocessable Content', + 500: 'Internal Server Error', +}; diff --git a/website/src/components/HTTP/Wrapper.module.css b/website/src/components/HTTP/Wrapper.module.css index d41d007a5872..99e2f9fc548c 100644 --- a/website/src/components/HTTP/Wrapper.module.css +++ b/website/src/components/HTTP/Wrapper.module.css @@ -22,4 +22,19 @@ div.fixtureCol .containedCode:global(.theme-code-block) { border-bottom-left-radius: 0; border-top-left-radius: 0; border-top-right-radius: 0; -} \ No newline at end of file +} +.status { + display: inline-block; + padding: 0 0.55em; + border: 1px solid rgba(46, 160, 67, 0.55); + border-radius: 999px; + background: rgba(46, 160, 67, 0.2); + color: #7ee2a8; + font-weight: 600; + line-height: 1.5; +} +.status.error { + border-color: rgba(248, 81, 73, 0.55); + background: rgba(248, 81, 73, 0.2); + color: #ffa198; +} diff --git a/website/src/components/Playground/editor/EditorSurface.tsx b/website/src/components/Playground/editor/EditorSurface.tsx index 4f1a9a46a8cf..45ee002db31b 100644 --- a/website/src/components/Playground/editor/EditorSurface.tsx +++ b/website/src/components/Playground/editor/EditorSurface.tsx @@ -238,7 +238,7 @@ function EditorHeader({ : null} {controls != null ? -
+
{title}
{controls}
diff --git a/website/src/components/Playground/monaco/theme.ts b/website/src/components/Playground/monaco/theme.ts index 5c8a2549d62f..e4c57f2afda5 100644 --- a/website/src/components/Playground/monaco/theme.ts +++ b/website/src/components/Playground/monaco/theme.ts @@ -28,6 +28,14 @@ export function definePrismTheme(monaco: typeof Monaco) { 'editor.foreground': '#bfc7d5', //'editor.lineHighlightBorder': '#33384d', 'editor.inactiveSelectionBackground': '#484d5b', + // Diffs: highlight only the changed characters, never whole lines + 'diffEditor.insertedLineBackground': '#00000000', + 'diffEditor.removedLineBackground': '#00000000', + 'diffEditorGutter.insertedLineBackground': '#00000000', + 'diffEditorGutter.removedLineBackground': '#00000000', + 'diffEditor.insertedTextBackground': '#3fb95040', + 'diffEditor.removedTextBackground': '#f8514955', + 'diffEditor.diagonalFill': '#00000000', }, }); } diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index d383af6c7030..fc6aac0c50e1 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -31,7 +31,6 @@ border-bottom-right-radius: var(--ifm-global-radius); } - .playgroundContainer.row .hidden { display: none; } @@ -69,7 +68,8 @@ cursor: pointer; } .playgroundHeader.clickable:hover { - background-color: var(--ifm-color-emphasis-500); + background-color: var(--pg-tab-hover-bg); + color: var(--pg-tab-hover); } .playgroundHeader.noupper { text-transform: none; @@ -85,13 +85,33 @@ padding: 0.75rem var(--ifm-pre-padding); } -body { - --ifm-color-playground-tab-selected: var(--ifm-color-emphasis-300); +/* Editor tab bar. Light mode shares the Live Preview header grey so the top + row reads as one band; dark mode sits one step lighter than the code. */ +:root { + --pg-bar: var(--ifm-color-emphasis-200); + --pg-bar-edge: transparent; + --pg-tab: var(--ifm-color-emphasis-700); + --pg-tab-hover-bg: var(--ifm-color-emphasis-300); + --pg-tab-hover: var(--ifm-color-emphasis-900); + --pg-tab-selected: #e3e8f2; + --pg-subbar: var(--ifm-color-emphasis-300); +} +:root[data-theme='dark'], +/* EndpointPlayground's right column is code, so it always uses the dark bar */ +.endpointPlayground { + --pg-bar: #343a52; + --pg-bar-edge: #23273a; + --pg-tab: rgba(191, 199, 213, 0.7); + --pg-tab-hover-bg: #3b415b; + --pg-tab-hover: #e3e8f2; + --pg-subbar: #3b415b; } +.playgroundHeader.tabControls, div.playgroundTextEdit > .playgroundHeader { - background: var(--ifm-color-emphasis-600); - color: var(--ifm-color-content-inverse); + background: var(--pg-bar); + color: var(--pg-tab); + box-shadow: inset 0 -1px 0 var(--pg-bar-edge); } .playgroundContainer:not(.row) > div.playgroundTextEdit @@ -99,10 +119,6 @@ div.playgroundTextEdit > .playgroundHeader { border-top-right-radius: var(--ifm-global-radius); border-top-left-radius: var(--ifm-global-radius); } -div > .playgroundHeader.subtabs { - color: var(--ifm-color-content); - background: var(--ifm-color-playground-tab-selected); -} .playgroundEditor { /*rtl:ignore*/ @@ -182,7 +198,8 @@ div > .playgroundHeader.subtabs { .debugToggle { writing-mode: vertical-rl; text-orientation: upright; - letter-spacing: 0.08rem; + letter-spacing: -0.04em; + line-height: 1.05; text-transform: uppercase; font-weight: 500; background: var(--ifm-color-emphasis-200); @@ -196,6 +213,10 @@ div > .playgroundHeader.subtabs { .debugToggle:hover { background-color: var(--ifm-color-emphasis-300); } +/* room between the last letter and the arrow */ +.debugToggle .arrow { + margin-inline-start: 0.9em; +} .hidden { display: none; @@ -229,28 +250,32 @@ div > .playgroundHeader.subtabs { align-items: center; } .tab:hover { - background-color: var(--ifm-color-emphasis-700); + background-color: var(--pg-tab-hover-bg); + color: var(--pg-tab-hover); } -.tab.selected { - background-color: var(--ifm-color-playground-tab-selected); - color: var(--ifm-color-content); -} -.subtabs .tab.selected { - background-color: var(--ifm-color-emphasis-600); - color: var(--ifm-color-content-inverse); -} -.subtabs .tab.selected:hover { - background-color: var(--ifm-color-emphasis-600); -} -.subtabs .tab { - padding: 0.4rem 0.75rem; +/* Selected tab joins the code panel below; inset edge avoids layout shift */ +.tab.selected, +.tab.selected:hover { + background-color: var(--monoco-code-background); + color: var(--pg-tab-selected); + box-shadow: inset 0 2px 0 var(--ifm-color-primary); } .small .tab { padding: 0.4rem 0.75rem; } +/* With header controls (e.g. homepage demos) the selected control joins the + file subtab row below it, and that row's selected file joins the code */ +div.playgroundTextEdit > .playgroundHeader.subtabs { + background: var(--pg-subbar); +} .subtabs .tab:hover { - background-color: var(--ifm-color-emphasis-400); + background-color: var(--pg-bar); +} +.controlTabs .tab.selected, +.controlTabs .tab.selected:hover { + background-color: var(--pg-subbar); + color: var(--pg-tab-hover); } :root .playgroundPreview { @@ -265,9 +290,37 @@ div > .playgroundHeader.subtabs { .playgroundPreview button { margin-bottom: 8px; margin-right: 8px; - padding: 2px 6px; + padding: 0.3rem 0.8rem; + border: 1px solid var(--ifm-color-emphasis-400); + border-radius: 6px; + background: var(--ifm-background-surface-color); + color: var(--ifm-font-color-base); + font: inherit; + font-size: 0.9rem; + font-weight: 500; + line-height: 1.4; + cursor: pointer; +} +.playgroundPreview button:hover { + border-color: var(--ifm-color-primary); + color: var(--ifm-color-primary); } -.playgroundPreview form input[type="text"] { +.playgroundPreview input[type='text'], +.playgroundPreview textarea { + padding: 0.3rem 0.5rem; + border: 1px solid var(--ifm-color-emphasis-400); + border-radius: 6px; + background: var(--ifm-background-surface-color); + color: var(--ifm-font-color-base); + font: inherit; +} +.playgroundPreview input[type='checkbox'] { + width: 1rem; + height: 1rem; + vertical-align: -2px; + accent-color: var(--ifm-color-primary); +} +.playgroundPreview form input[type='text'] { width: 100%; } .playgroundPreview form textarea { @@ -291,7 +344,7 @@ div > .playgroundHeader.subtabs { line-height: 1.1; margin-bottom: 4px; } -.playgroundPreview :global(.listItem) > label input[type="text"] { +.playgroundPreview :global(.listItem) > label input[type='text'] { width: calc(100% - 21px); } .playgroundPreview :global(.listItem.spaced) { @@ -431,7 +484,6 @@ div.fixtureJson { position: auto !important; } - /* Side-by-side when the playground itself has enough width (docs sidebar no longer forces an incorrect stack on homepage/blog). */ @container playground (min-width: 720px) { @@ -461,3 +513,47 @@ div.fixtureJson { border-top-right-radius: 0; } } + +/* EndpointPlayground: every header bar matches the dark editor tab bar */ +.endpointPlayground .playgroundHeader { + display: flex; + align-items: center; + box-sizing: border-box; + height: 34px; + padding: 0 0.75rem; + background: var(--pg-bar); + color: var(--pg-tab); + box-shadow: inset 0 -1px 0 var(--pg-bar-edge); + text-transform: none; + font-size: 80%; + font-weight: 500; + letter-spacing: 0.04rem; +} +.endpointPlayground .playgroundHeader.tabControls { + padding: 0; +} +.endpointPlayground .playgroundHeader .tab { + height: 33px; + padding: 0 0.75rem; +} +@container playground (min-width: 720px) { + .endpointPlayground > div:last-child { + border-left: 1px solid var(--pg-bar-edge); + } +} + +/* Slim Before/After labels above DiffEditor panes; offsets set from Monaco layout */ +.diffLabels { + position: relative; + height: 22px; + background: var(--monoco-code-background); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + color: rgba(191, 199, 213, 0.55); + font: 500 11px / 22px var(--ifm-font-family-base); + letter-spacing: 0.06em; + text-transform: uppercase; +} +.diffLabels > span { + position: absolute; + left: 0.75rem; +} From 57d3b6f8965480400162acace94c99ececd948ef Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 22:39:21 +0000 Subject: [PATCH 02/15] docs(website): Simplify playground chrome styles Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- website/src/components/DiffEditorMonaco.tsx | 19 +++--- website/src/components/HTTP/Response.tsx | 2 +- .../components/Playground/styles.module.css | 61 ++++++++----------- 3 files changed, 35 insertions(+), 47 deletions(-) diff --git a/website/src/components/DiffEditorMonaco.tsx b/website/src/components/DiffEditorMonaco.tsx index c31e19121227..30502db1d6ae 100644 --- a/website/src/components/DiffEditorMonaco.tsx +++ b/website/src/components/DiffEditorMonaco.tsx @@ -36,18 +36,19 @@ export default function DiffEditor({ documents, fallback }: DiffMonacoProps) { const panes = [editor.getOriginalEditor(), editor.getModifiedEditor()]; const updateOffsets = () => { const left = editor.getContainerDomNode().getBoundingClientRect().left; - setLabelOffsets( - panes.map( - pane => - (pane.getDomNode()?.getBoundingClientRect().left ?? left) - - left + - pane.getLayoutInfo().contentLeft, - ), + const next = panes.map( + pane => + (pane.getDomNode()?.getBoundingClientRect().left ?? left) - + left + + pane.getLayoutInfo().contentLeft, + ); + setLabelOffsets(prev => + prev?.every((offset, i) => offset === next[i]) ? prev : next, ); }; updateOffsets(); - // disposed along with the panes - panes.forEach(pane => pane.onDidLayoutChange(updateOffsets)); + // Both panes lay out together; disposed along with the pane + panes[1].onDidLayoutChange(updateOffsets); }, [handleMount], ); diff --git a/website/src/components/HTTP/Response.tsx b/website/src/components/HTTP/Response.tsx index a659e389b953..9f633333c6b9 100644 --- a/website/src/components/HTTP/Response.tsx +++ b/website/src/components/HTTP/Response.tsx @@ -14,7 +14,7 @@ export default function Response({ response, status }: Props) { [styles.error]: status >= 400, })} > - {`${status} ${STATUS_TEXT[status] ?? ''}`.trim()} + {STATUS_TEXT[status] ? `${status} ${STATUS_TEXT[status]}` : status}
diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index fc6aac0c50e1..8039689aee03 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -94,7 +94,7 @@ --pg-tab-hover-bg: var(--ifm-color-emphasis-300); --pg-tab-hover: var(--ifm-color-emphasis-900); --pg-tab-selected: #e3e8f2; - --pg-subbar: var(--ifm-color-emphasis-300); + --pg-tab-selected-bg: var(--monoco-code-background); } :root[data-theme='dark'], /* EndpointPlayground's right column is code, so it always uses the dark bar */ @@ -104,11 +104,11 @@ --pg-tab: rgba(191, 199, 213, 0.7); --pg-tab-hover-bg: #3b415b; --pg-tab-hover: #e3e8f2; - --pg-subbar: #3b415b; } .playgroundHeader.tabControls, -div.playgroundTextEdit > .playgroundHeader { +div.playgroundTextEdit > .playgroundHeader, +.endpointPlayground .playgroundHeader { background: var(--pg-bar); color: var(--pg-tab); box-shadow: inset 0 -1px 0 var(--pg-bar-edge); @@ -158,6 +158,8 @@ div.playgroundTextEdit > .playgroundHeader { .arrow.vertical { float: right; margin-right: 0; + /* room between the last letter and the arrow */ + margin-inline-start: 0.9em; } .arrow.right { transform: rotateZ(0deg); @@ -213,10 +215,6 @@ div.playgroundTextEdit > .playgroundHeader { .debugToggle:hover { background-color: var(--ifm-color-emphasis-300); } -/* room between the last letter and the arrow */ -.debugToggle .arrow { - margin-inline-start: 0.9em; -} .hidden { display: none; @@ -255,9 +253,8 @@ div.playgroundTextEdit > .playgroundHeader { } /* Selected tab joins the code panel below; inset edge avoids layout shift */ -.tab.selected, -.tab.selected:hover { - background-color: var(--monoco-code-background); +.tab.selected { + background-color: var(--pg-tab-selected-bg); color: var(--pg-tab-selected); box-shadow: inset 0 2px 0 var(--ifm-color-primary); } @@ -267,15 +264,14 @@ div.playgroundTextEdit > .playgroundHeader { /* With header controls (e.g. homepage demos) the selected control joins the file subtab row below it, and that row's selected file joins the code */ div.playgroundTextEdit > .playgroundHeader.subtabs { - background: var(--pg-subbar); + background: var(--pg-tab-hover-bg); } -.subtabs .tab:hover { +.subtabs .tab:not(.selected):hover { background-color: var(--pg-bar); } -.controlTabs .tab.selected, -.controlTabs .tab.selected:hover { - background-color: var(--pg-subbar); - color: var(--pg-tab-hover); +.controlTabs { + --pg-tab-selected-bg: var(--pg-tab-hover-bg); + --pg-tab-selected: var(--pg-tab-hover); } :root .playgroundPreview { @@ -287,15 +283,19 @@ div.playgroundTextEdit > .playgroundHeader.subtabs { /** DEMO STYLES * Place any styles needed for demos here */ -.playgroundPreview button { - margin-bottom: 8px; - margin-right: 8px; - padding: 0.3rem 0.8rem; +.playgroundPreview button, +.playgroundPreview input[type='text'], +.playgroundPreview textarea { border: 1px solid var(--ifm-color-emphasis-400); border-radius: 6px; background: var(--ifm-background-surface-color); color: var(--ifm-font-color-base); font: inherit; +} +.playgroundPreview button { + margin-bottom: 8px; + margin-right: 8px; + padding: 0.3rem 0.8rem; font-size: 0.9rem; font-weight: 500; line-height: 1.4; @@ -308,11 +308,6 @@ div.playgroundTextEdit > .playgroundHeader.subtabs { .playgroundPreview input[type='text'], .playgroundPreview textarea { padding: 0.3rem 0.5rem; - border: 1px solid var(--ifm-color-emphasis-400); - border-radius: 6px; - background: var(--ifm-background-surface-color); - color: var(--ifm-font-color-base); - font: inherit; } .playgroundPreview input[type='checkbox'] { width: 1rem; @@ -509,24 +504,21 @@ div.fixtureJson { .playgroundContainer.endpointPlayground > div:last-child { flex: 50 1 0; } + .playgroundContainer.endpointPlayground > div:last-child { + border-left: 1px solid var(--pg-bar-edge); + } .playgroundContainer.row .playgroundHeader.tabControls:first-of-type { border-top-right-radius: 0; } } -/* EndpointPlayground: every header bar matches the dark editor tab bar */ +/* EndpointPlayground: left tab bar and right fixture headers share one height */ .endpointPlayground .playgroundHeader { display: flex; align-items: center; box-sizing: border-box; height: 34px; padding: 0 0.75rem; - background: var(--pg-bar); - color: var(--pg-tab); - box-shadow: inset 0 -1px 0 var(--pg-bar-edge); - text-transform: none; - font-size: 80%; - font-weight: 500; letter-spacing: 0.04rem; } .endpointPlayground .playgroundHeader.tabControls { @@ -536,11 +528,6 @@ div.fixtureJson { height: 33px; padding: 0 0.75rem; } -@container playground (min-width: 720px) { - .endpointPlayground > div:last-child { - border-left: 1px solid var(--pg-bar-edge); - } -} /* Slim Before/After labels above DiffEditor panes; offsets set from Monaco layout */ .diffLabels { From d1d87e196e07e0ec109eac0e292d4d92ffe02425 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 22:47:14 +0000 Subject: [PATCH 03/15] docs(website): Limit preview control skin to unclassed elements Keeps DesignSystem rt-* inputs and vote arrow buttons on their own chrome. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- .../components/Playground/styles.module.css | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index 8039689aee03..97b862b3d749 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -283,30 +283,35 @@ div.playgroundTextEdit > .playgroundHeader.subtabs { /** DEMO STYLES * Place any styles needed for demos here */ -.playgroundPreview button, -.playgroundPreview input[type='text'], -.playgroundPreview textarea { +.playgroundPreview button { + margin-bottom: 8px; + margin-right: 8px; + padding: 2px 6px; +} +/* Base skin for plain controls only; classed ones (DesignSystem rt-*, vote + arrows) keep their own chrome. :where() keeps specificity unchanged. */ +.playgroundPreview button:where(:not([class])), +.playgroundPreview input[type='text']:where(:not([class])), +.playgroundPreview textarea:where(:not([class])) { border: 1px solid var(--ifm-color-emphasis-400); border-radius: 6px; background: var(--ifm-background-surface-color); color: var(--ifm-font-color-base); font: inherit; } -.playgroundPreview button { - margin-bottom: 8px; - margin-right: 8px; +.playgroundPreview button:where(:not([class])) { padding: 0.3rem 0.8rem; font-size: 0.9rem; font-weight: 500; line-height: 1.4; cursor: pointer; } -.playgroundPreview button:hover { +.playgroundPreview button:where(:not([class])):hover { border-color: var(--ifm-color-primary); color: var(--ifm-color-primary); } -.playgroundPreview input[type='text'], -.playgroundPreview textarea { +.playgroundPreview input[type='text']:where(:not([class])), +.playgroundPreview textarea:where(:not([class])) { padding: 0.3rem 0.5rem; } .playgroundPreview input[type='checkbox'] { From a74660ea22b19a426e04dcad9e9ca0894eb7108b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 23:10:46 +0000 Subject: [PATCH 04/15] fix(website): Build previews whose previous deploy is the commit itself Vercel can report the commit being built as VERCEL_GIT_PREVIOUS_SHA on a preview branch with no successful preview yet. Diffing HEAD against itself is empty, so every new site PR's preview was skipped. Ignore that value and fall back to the merge-base with master. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- .cursor/rules/ci-config.mdc | 1 + website/scripts/vercel-ignore.sh | 4 ++++ website/scripts/vercel-ignore.test.sh | 1 + 3 files changed, 6 insertions(+) diff --git a/.cursor/rules/ci-config.mdc b/.cursor/rules/ci-config.mdc index 62fbde22266a..dc87b0528998 100644 --- a/.cursor/rules/ci-config.mdc +++ b/.cursor/rules/ci-config.mdc @@ -32,4 +32,5 @@ alwaysApply: false - Vercel's ignore step: exit 0 skips, anything else builds, so fail open. Never diff only `HEAD^`: a master merge or multi-commit push makes it wrong. Run `vercel-ignore.test.sh` after changes. - `git.deploymentEnabled: false` doesn't stick (dashboard overrides it); skipped pushes still show as canceled deployments. +- `VERCEL_GIT_PREVIOUS_SHA` can equal the commit being built on a preview branch with no successful preview yet; never diff it against `HEAD` (it always skips). - Previews on `renovate/*` skip when the only site changes are website `package.json` or lockfiles; production is unchanged. diff --git a/website/scripts/vercel-ignore.sh b/website/scripts/vercel-ignore.sh index 2c78683a45bb..eeda3c643b2b 100755 --- a/website/scripts/vercel-ignore.sh +++ b/website/scripts/vercel-ignore.sh @@ -97,6 +97,10 @@ if has_rev 'HEAD^2' && master="$(upstream)" && is_ancestor 'HEAD^2' "$master"; t decide 'HEAD^2' HEAD "preview changes vs master (merge)" fi +# A preview's previous deploy can be the commit being built (a branch with no +# successful preview yet). Diffing it against itself would always skip. +[ -n "$prev" ] && [ "$(git rev-parse -q --verify "$prev^{commit}")" = "$(git rev-parse HEAD)" ] && prev="" + is_ancestor "$prev" HEAD && decide "$prev" HEAD "preview changes since ${prev:0:12}" if base="$(merge_base)" || { deepen && base="$(merge_base)"; }; then diff --git a/website/scripts/vercel-ignore.test.sh b/website/scripts/vercel-ignore.test.sh index b42eccc7ab51..17c3aae5383f 100755 --- a/website/scripts/vercel-ignore.test.sh +++ b/website/scripts/vercel-ignore.test.sh @@ -108,6 +108,7 @@ expect skip "preview package follow-up after site deploy" feature "$page_sha" commit "playground source" website/src/components/Playground/transformCode.ts expect build "preview playground source since last deploy" feature "$page_sha" +expect build "preview whose previous deploy is the commit itself" feature "$(git -C "$repo" rev-parse HEAD)" # Multi-commit branch with no prior deploy: the site edit is not the tip. git -C "$repo" checkout -b stacked master >/dev/null 2>&1 From d545ce170ba98092e04791101cde5eff305dcf54 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 23:11:24 +0000 Subject: [PATCH 05/15] Revert "fix(website): Build previews whose previous deploy is the commit itself" The preview for that commit was still skipped, so this was not the cause. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- .cursor/rules/ci-config.mdc | 1 - website/scripts/vercel-ignore.sh | 4 ---- website/scripts/vercel-ignore.test.sh | 1 - 3 files changed, 6 deletions(-) diff --git a/.cursor/rules/ci-config.mdc b/.cursor/rules/ci-config.mdc index dc87b0528998..62fbde22266a 100644 --- a/.cursor/rules/ci-config.mdc +++ b/.cursor/rules/ci-config.mdc @@ -32,5 +32,4 @@ alwaysApply: false - Vercel's ignore step: exit 0 skips, anything else builds, so fail open. Never diff only `HEAD^`: a master merge or multi-commit push makes it wrong. Run `vercel-ignore.test.sh` after changes. - `git.deploymentEnabled: false` doesn't stick (dashboard overrides it); skipped pushes still show as canceled deployments. -- `VERCEL_GIT_PREVIOUS_SHA` can equal the commit being built on a preview branch with no successful preview yet; never diff it against `HEAD` (it always skips). - Previews on `renovate/*` skip when the only site changes are website `package.json` or lockfiles; production is unchanged. diff --git a/website/scripts/vercel-ignore.sh b/website/scripts/vercel-ignore.sh index eeda3c643b2b..2c78683a45bb 100755 --- a/website/scripts/vercel-ignore.sh +++ b/website/scripts/vercel-ignore.sh @@ -97,10 +97,6 @@ if has_rev 'HEAD^2' && master="$(upstream)" && is_ancestor 'HEAD^2' "$master"; t decide 'HEAD^2' HEAD "preview changes vs master (merge)" fi -# A preview's previous deploy can be the commit being built (a branch with no -# successful preview yet). Diffing it against itself would always skip. -[ -n "$prev" ] && [ "$(git rev-parse -q --verify "$prev^{commit}")" = "$(git rev-parse HEAD)" ] && prev="" - is_ancestor "$prev" HEAD && decide "$prev" HEAD "preview changes since ${prev:0:12}" if base="$(merge_base)" || { deepen && base="$(merge_base)"; }; then diff --git a/website/scripts/vercel-ignore.test.sh b/website/scripts/vercel-ignore.test.sh index 17c3aae5383f..b42eccc7ab51 100755 --- a/website/scripts/vercel-ignore.test.sh +++ b/website/scripts/vercel-ignore.test.sh @@ -108,7 +108,6 @@ expect skip "preview package follow-up after site deploy" feature "$page_sha" commit "playground source" website/src/components/Playground/transformCode.ts expect build "preview playground source since last deploy" feature "$page_sha" -expect build "preview whose previous deploy is the commit itself" feature "$(git -C "$repo" rev-parse HEAD)" # Multi-commit branch with no prior deploy: the site edit is not the tip. git -C "$repo" checkout -b stacked master >/dev/null 2>&1 From 1286f972bc233845af0960a942ab89b35dedef6f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 00:43:57 +0000 Subject: [PATCH 06/15] fix(website): Stop skipping every site preview on Vercel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vercel logged "skip — preview changes vs master" for site PRs. Its clone can carry a master ref at the commit being built, so the merge-base was HEAD and the diff was empty. Always fetch master, never treat a ref equal to HEAD as master, and log the base sha. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- .cursor/rules/ci-config.mdc | 1 + website/scripts/vercel-ignore.sh | 21 +++++++++++++++------ website/scripts/vercel-ignore.test.sh | 9 +++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.cursor/rules/ci-config.mdc b/.cursor/rules/ci-config.mdc index 62fbde22266a..aaf207295788 100644 --- a/.cursor/rules/ci-config.mdc +++ b/.cursor/rules/ci-config.mdc @@ -32,4 +32,5 @@ alwaysApply: false - Vercel's ignore step: exit 0 skips, anything else builds, so fail open. Never diff only `HEAD^`: a master merge or multi-commit push makes it wrong. Run `vercel-ignore.test.sh` after changes. - `git.deploymentEnabled: false` doesn't stick (dashboard overrides it); skipped pushes still show as canceled deployments. +- Vercel's clone can have a `master`/`origin/master` ref at the commit being built, so always fetch master and never use a ref equal to `HEAD` as the base (it makes every preview skip). - Previews on `renovate/*` skip when the only site changes are website `package.json` or lockfiles; production is unchanged. diff --git a/website/scripts/vercel-ignore.sh b/website/scripts/vercel-ignore.sh index 2c78683a45bb..bc7cce15921d 100755 --- a/website/scripts/vercel-ignore.sh +++ b/website/scripts/vercel-ignore.sh @@ -51,12 +51,21 @@ is_ancestor() { [ -n "$1" ] && git merge-base --is-ancestor "$1" "$2" 2>/dev/null } -# Prints master's sha. Vercel clones shallow, so fetch it if missing; the -# timeout keeps a hung network call from holding the build machine. +# Prints master's sha. Always fetch it: Vercel's clone can carry a `master` or +# `origin/master` ref that points at the commit being built, which makes every +# preview diff empty. The timeout keeps a hung network call from holding the +# build machine. A ref equal to HEAD is never trusted as master. upstream() { - has_rev origin/master || - timeout 15 git fetch -q --no-tags --depth=80 origin master:refs/remotes/origin/master 2>/dev/null - git rev-parse --verify -q origin/master || git rev-parse --verify -q master + local head ref sha + timeout 15 git fetch -q --no-tags --depth=80 origin +master:refs/remotes/origin/master 2>/dev/null + head="$(git rev-parse HEAD)" + for ref in origin/master master; do + sha="$(git rev-parse --verify -q "$ref^{commit}")" && [ "$sha" != "$head" ] && { + echo "$sha" + return 0 + } + done + return 1 } # Vercel clones about 10 commits deep. Fetch more of this branch and master @@ -100,7 +109,7 @@ fi is_ancestor "$prev" HEAD && decide "$prev" HEAD "preview changes since ${prev:0:12}" if base="$(merge_base)" || { deepen && base="$(merge_base)"; }; then - decide "$base" HEAD "preview changes vs master" + decide "$base" HEAD "preview changes vs master (base ${base:0:12})" fi # Without a base, the tip commit alone can't prove earlier commits left the diff --git a/website/scripts/vercel-ignore.test.sh b/website/scripts/vercel-ignore.test.sh index b42eccc7ab51..e6e85bdfe89b 100755 --- a/website/scripts/vercel-ignore.test.sh +++ b/website/scripts/vercel-ignore.test.sh @@ -163,6 +163,15 @@ commit "unrelated site" website/src/pages/index.js commit "unrelated pkg" packages/core/src/index.ts expect build "preview without merge-base builds" unrelated +# Vercel's clone can carry a master ref at the commit being built. Comparing +# HEAD with itself would always skip, so that ref is not a base. +git -C "$repo" checkout -b clone-master master >/dev/null 2>&1 +commit "clone-master page" docs/core/api/CloneMaster.md +real_master="$(git -C "$repo" rev-parse master)" +git -C "$repo" update-ref refs/heads/master HEAD +expect build "preview when the clone's master ref is HEAD" clone-master +git -C "$repo" update-ref refs/heads/master "$real_master" + # gh-pages branches never build, even if website files differ. expect skip "gh-pages branch" gh-pages-bench From ebbd71f664962dc135fccda358c39f0e0323bd90 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 00:46:56 +0000 Subject: [PATCH 07/15] fix(website): Fetch master once so deepen() keeps its history Refetching master with --depth=80 inside upstream() undid deepen(), so PRs forked more than 80 commits behind master fell back to a full build. Fetch once before the preview checks; upstream() only looks refs up. Adds a file:// shallow-clone test with a far fork point and a master ref at HEAD. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- website/scripts/vercel-ignore.sh | 12 +++++++----- website/scripts/vercel-ignore.test.sh | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/website/scripts/vercel-ignore.sh b/website/scripts/vercel-ignore.sh index bc7cce15921d..e154c89f9331 100755 --- a/website/scripts/vercel-ignore.sh +++ b/website/scripts/vercel-ignore.sh @@ -51,13 +51,10 @@ is_ancestor() { [ -n "$1" ] && git merge-base --is-ancestor "$1" "$2" 2>/dev/null } -# Prints master's sha. Always fetch it: Vercel's clone can carry a `master` or -# `origin/master` ref that points at the commit being built, which makes every -# preview diff empty. The timeout keeps a hung network call from holding the -# build machine. A ref equal to HEAD is never trusted as master. +# Prints master's sha. A ref equal to HEAD is never trusted as master: Vercel's +# clone can carry one at the commit being built, which empties every diff. upstream() { local head ref sha - timeout 15 git fetch -q --no-tags --depth=80 origin +master:refs/remotes/origin/master 2>/dev/null head="$(git rev-parse HEAD)" for ref in origin/master master; do sha="$(git rev-parse --verify -q "$ref^{commit}")" && [ "$sha" != "$head" ] && { @@ -90,6 +87,11 @@ if [[ "${VERCEL_GIT_COMMIT_REF:-}" =~ ^(master|rest-hooks-site)$ || "${VERCEL_EN build "no previous production deploy to compare" fi +# Previews need the real master. Fetch it once, forced, so a clone-provided +# ref is replaced; refetching later with --depth would undo deepen(). The +# timeout keeps a hung network call from holding the build machine. +timeout 15 git fetch -q --no-tags --depth=80 origin +master:refs/remotes/origin/master 2>/dev/null + # Renovate previews skip when only website dependency manifests or lockfiles # changed. Site or docs source changes still build. if [[ "${VERCEL_GIT_COMMIT_REF:-}" == renovate/* ]]; then diff --git a/website/scripts/vercel-ignore.test.sh b/website/scripts/vercel-ignore.test.sh index e6e85bdfe89b..2e24e08fbe35 100755 --- a/website/scripts/vercel-ignore.test.sh +++ b/website/scripts/vercel-ignore.test.sh @@ -212,4 +212,25 @@ git -C "$repo" checkout master >/dev/null 2>&1 commit "master bump" website/package.json website/yarn.lock expect build "master website manifest" master "$(parent)" +# --- Vercel-like shallow clone: fork point far behind master, and the clone +# carries a master ref at HEAD. deepen() must reach the real merge-base. +git -C "$repo" checkout -b far-pkg master >/dev/null 2>&1 +commit "far pkg" packages/core/src/far.ts +git -C "$repo" checkout -b far-site master >/dev/null 2>&1 +commit "far site" docs/core/api/Far.md +git -C "$repo" checkout master >/dev/null 2>&1 +for i in $(seq 1 100); do commit "master filler $i" packages/rest/src/filler.ts; done +origin_repo="$repo" +for branch in far-pkg far-site; do + clone="$(mktemp -d)" + git clone -q --depth=10 --branch "$branch" "file://$origin_repo" "$clone" + git -C "$clone" branch -f master HEAD + repo="$clone" + want=skip + [ "$branch" = far-site ] && want=build + expect "$want" "shallow clone, fork far behind master ($branch)" "$branch" + repo="$origin_repo" + rm -rf "$clone" +done + echo "all vercel-ignore cases passed" From 4fbb3b636ef047f99890a8f8acdfb86dadc7c7a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 00:52:27 +0000 Subject: [PATCH 08/15] fix(website): Deepen master in its own fetch Under git 2.55 a single fetch of the branch and master with --deepen leaves master at its earlier depth, so forks far behind master found no merge base and always built. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- website/scripts/vercel-ignore.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/website/scripts/vercel-ignore.sh b/website/scripts/vercel-ignore.sh index e154c89f9331..34b493ac3f2c 100755 --- a/website/scripts/vercel-ignore.sh +++ b/website/scripts/vercel-ignore.sh @@ -66,11 +66,13 @@ upstream() { } # Vercel clones about 10 commits deep. Fetch more of this branch and master -# when a comparison base is out of reach. +# when a comparison base is out of reach. One fetch per ref: when both go in +# one call, newer git (2.55) can leave master at its old depth. deepen() { - [ -n "${VERCEL_GIT_COMMIT_REF:-}" ] && - timeout 30 git fetch -q --no-tags --deepen=300 origin "$VERCEL_GIT_COMMIT_REF" \ - '+refs/heads/master:refs/remotes/origin/master' 2>/dev/null + [ -n "${VERCEL_GIT_COMMIT_REF:-}" ] || return 1 + timeout 30 git fetch -q --no-tags --deepen=300 origin "$VERCEL_GIT_COMMIT_REF" 2>/dev/null + timeout 30 git fetch -q --no-tags --deepen=300 origin \ + '+refs/heads/master:refs/remotes/origin/master' 2>/dev/null } merge_base() { From 66be2c75f7f4fb9e6f38c6d18f3dfd66a37e2c78 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 00:59:29 +0000 Subject: [PATCH 09/15] fix(website): Give the preview side the editor's bar colors The live preview header and Store toggle kept the neutral grey while the editor bar turned slate, so dark mode showed two clashing bands. All playground headers now share the bar color, the dark preview body takes a slate tint, and the line between the tab rows is gone. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- .../components/Playground/styles.module.css | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index 97b862b3d749..02662f0ef8d3 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -54,8 +54,8 @@ padding: 0.75rem; text-transform: uppercase; font-weight: bold; - background: var(--ifm-color-emphasis-200); - color: var(--ifm-color-content); + background: var(--pg-bar); + color: var(--pg-tab); font-size: var(--ifm-code-font-size); } .playgroundHeader.small { @@ -85,8 +85,9 @@ padding: 0.75rem var(--ifm-pre-padding); } -/* Editor tab bar. Light mode shares the Live Preview header grey so the top - row reads as one band; dark mode sits one step lighter than the code. */ +/* Playground chrome. Every header (editor tabs, Live Preview, Store) shares + one bar color so the top row reads as one band. Dark mode bars sit one step + lighter than the code, and the preview body takes the same slate tint. */ :root { --pg-bar: var(--ifm-color-emphasis-200); --pg-bar-edge: transparent; @@ -100,11 +101,16 @@ /* EndpointPlayground's right column is code, so it always uses the dark bar */ .endpointPlayground { --pg-bar: #343a52; - --pg-bar-edge: #23273a; --pg-tab: rgba(191, 199, 213, 0.7); --pg-tab-hover-bg: #3b415b; --pg-tab-hover: #e3e8f2; } +:root[data-theme='dark'] { + --pg-preview-bg: hsl(229, 16%, 19%); +} +.endpointPlayground { + --pg-bar-edge: #23273a; +} .playgroundHeader.tabControls, div.playgroundTextEdit > .playgroundHeader, @@ -176,7 +182,7 @@ div.playgroundTextEdit > .playgroundHeader, :global(.col) .playgroundPreview { padding: 1rem; - background-color: var(--ifm-pre-background); + background-color: var(--pg-preview-bg, var(--ifm-pre-background)); flex: 1 4 30%; overflow: auto; } @@ -204,8 +210,8 @@ div.playgroundTextEdit > .playgroundHeader, line-height: 1.05; text-transform: uppercase; font-weight: 500; - background: var(--ifm-color-emphasis-200); - color: var(--ifm-color-content); + background: var(--pg-bar); + color: var(--pg-tab); font-size: var(--ifm-code-font-size); padding: 0.7em 0.1em 1em 0.1em; cursor: pointer; @@ -213,7 +219,8 @@ div.playgroundTextEdit > .playgroundHeader, } .debugToggle:hover { - background-color: var(--ifm-color-emphasis-300); + background-color: var(--pg-tab-hover-bg); + color: var(--pg-tab-hover); } .hidden { From d0ab4949a09c92f5b6c732ceb943c4314c342e09 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 01:03:14 +0000 Subject: [PATCH 10/15] fix(website): Tone dark playground chrome down to near-neutral grey The slate bars and preview tint read too blue in dark mode. Keep the hue but drop saturation so the chrome sits close to the site's neutral greys. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- website/src/components/Playground/styles.module.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index 02662f0ef8d3..8f290bbccfa4 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -87,7 +87,7 @@ /* Playground chrome. Every header (editor tabs, Live Preview, Store) shares one bar color so the top row reads as one band. Dark mode bars sit one step - lighter than the code, and the preview body takes the same slate tint. */ + lighter than the code in a near-neutral grey, matched by the preview body. */ :root { --pg-bar: var(--ifm-color-emphasis-200); --pg-bar-edge: transparent; @@ -100,13 +100,13 @@ :root[data-theme='dark'], /* EndpointPlayground's right column is code, so it always uses the dark bar */ .endpointPlayground { - --pg-bar: #343a52; + --pg-bar: hsl(229, 8%, 26%); --pg-tab: rgba(191, 199, 213, 0.7); - --pg-tab-hover-bg: #3b415b; + --pg-tab-hover-bg: hsl(229, 8%, 29%); --pg-tab-hover: #e3e8f2; } :root[data-theme='dark'] { - --pg-preview-bg: hsl(229, 16%, 19%); + --pg-preview-bg: hsl(229, 4%, 18%); } .endpointPlayground { --pg-bar-edge: #23273a; From e7bdc5db3f5ba23c8076f32a73be389f7a908a74 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 01:04:08 +0000 Subject: [PATCH 11/15] refactor(website): Drop bar colors the base header rule already sets Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- website/src/components/Playground/styles.module.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index 8f290bbccfa4..7aa7caf13159 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -115,8 +115,6 @@ .playgroundHeader.tabControls, div.playgroundTextEdit > .playgroundHeader, .endpointPlayground .playgroundHeader { - background: var(--pg-bar); - color: var(--pg-tab); box-shadow: inset 0 -1px 0 var(--pg-bar-edge); } .playgroundContainer:not(.row) From f9a229357e2acddcc89dc1e42651dfb67e4052e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 01:04:34 +0000 Subject: [PATCH 12/15] fix(website): Desaturate the endpoint playground edge to match its bar Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- website/src/components/Playground/styles.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index 7aa7caf13159..aac4ce0d6bcd 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -109,7 +109,7 @@ --pg-preview-bg: hsl(229, 4%, 18%); } .endpointPlayground { - --pg-bar-edge: #23273a; + --pg-bar-edge: hsl(229, 8%, 17%); } .playgroundHeader.tabControls, From c966603b4ddef8bf3bf812bc376fd35ebc1c1815 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 01:23:20 +0000 Subject: [PATCH 13/15] fix(website): Use master's neutral greys for dark playground chrome Dark mode bars and the preview body go back to the Infima greys master used, now shared by every playground header. Only the always-dark endpoint playground keeps its own bar tokens. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- website/src/components/Playground/styles.module.css | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index aac4ce0d6bcd..b84d9b7c44bf 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -86,8 +86,7 @@ } /* Playground chrome. Every header (editor tabs, Live Preview, Store) shares - one bar color so the top row reads as one band. Dark mode bars sit one step - lighter than the code in a near-neutral grey, matched by the preview body. */ + one neutral bar color in both themes so the top row reads as one band. */ :root { --pg-bar: var(--ifm-color-emphasis-200); --pg-bar-edge: transparent; @@ -97,18 +96,12 @@ --pg-tab-selected: #e3e8f2; --pg-tab-selected-bg: var(--monoco-code-background); } -:root[data-theme='dark'], /* EndpointPlayground's right column is code, so it always uses the dark bar */ .endpointPlayground { --pg-bar: hsl(229, 8%, 26%); --pg-tab: rgba(191, 199, 213, 0.7); --pg-tab-hover-bg: hsl(229, 8%, 29%); --pg-tab-hover: #e3e8f2; -} -:root[data-theme='dark'] { - --pg-preview-bg: hsl(229, 4%, 18%); -} -.endpointPlayground { --pg-bar-edge: hsl(229, 8%, 17%); } @@ -180,7 +173,7 @@ div.playgroundTextEdit > .playgroundHeader, :global(.col) .playgroundPreview { padding: 1rem; - background-color: var(--pg-preview-bg, var(--ifm-pre-background)); + background-color: var(--ifm-pre-background); flex: 1 4 30%; overflow: auto; } From 6e372bb860893ea763538a44f4971b16383fc13a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 01:25:44 +0000 Subject: [PATCH 14/15] ci: Skip Codecov upload when the uploader download fails Ported from #4176 so unit_tests-latest is not blocked by the codecov uploader outage; it no-ops once #4176 lands on master. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- .circleci/config.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3054a37e5956..6a98ee7dcc55 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -249,13 +249,14 @@ jobs: elif [ "<< parameters.react-version >>" == "19.3" ]; then yarn test:ci --maxWorkers=4 --selectProjects ReactDOM --testPathPatterns 'packages/react/src/__tests__/concurrent-.*\.web\.tsx' 'packages/react/src/hooks/__tests__/useCacheState.web.tsx' else - curl -Os https://uploader.codecov.io/latest/linux/codecov; - chmod +x codecov; + curl -fsSO --connect-timeout 10 --max-time 60 https://uploader.codecov.io/latest/linux/codecov && chmod +x codecov || echo "Codecov uploader download failed; skipping coverage upload"; yarn run test:coverage --ci --maxWorkers=4 --selectProjects ReactDOM Node --coverageReporters=text-lcov > ./lcov.info; - if [ "$CODECOV_TOKEN" != "" ]; then - ./codecov -t ${CODECOV_TOKEN} < ./lcov.info || true; - else - ./codecov < ./lcov.info || true; + if [ -x ./codecov ]; then + if [ "$CODECOV_TOKEN" != "" ]; then + ./codecov -t ${CODECOV_TOKEN} < ./lcov.info || true; + else + ./codecov < ./lcov.info || true; + fi fi fi From 241cf9fd2b18ae805a66c342673f71e47510b142 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 01:46:20 +0000 Subject: [PATCH 15/15] fix(website): Restore full-contrast text in playground header bars Header and tab labels used a muted grey and read faintly; they now use the content color master used, and the endpoint playground's labels are opaque. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KR7TVj44kxYCnGtVXVkx3f --- website/src/components/Playground/styles.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index b84d9b7c44bf..612b450ee2de 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -90,7 +90,7 @@ :root { --pg-bar: var(--ifm-color-emphasis-200); --pg-bar-edge: transparent; - --pg-tab: var(--ifm-color-emphasis-700); + --pg-tab: var(--ifm-color-content); --pg-tab-hover-bg: var(--ifm-color-emphasis-300); --pg-tab-hover: var(--ifm-color-emphasis-900); --pg-tab-selected: #e3e8f2; @@ -99,7 +99,7 @@ /* EndpointPlayground's right column is code, so it always uses the dark bar */ .endpointPlayground { --pg-bar: hsl(229, 8%, 26%); - --pg-tab: rgba(191, 199, 213, 0.7); + --pg-tab: rgb(191, 199, 213); --pg-tab-hover-bg: hsl(229, 8%, 29%); --pg-tab-hover: #e3e8f2; --pg-bar-edge: hsl(229, 8%, 17%);