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 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/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/scripts/vercel-ignore.sh b/website/scripts/vercel-ignore.sh index 2c78683a45bb..34b493ac3f2c 100755 --- a/website/scripts/vercel-ignore.sh +++ b/website/scripts/vercel-ignore.sh @@ -51,20 +51,28 @@ 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. 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() { - 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 + 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 -# 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() { @@ -81,6 +89,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 @@ -100,7 +113,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..2e24e08fbe35 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 @@ -203,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" diff --git a/website/src/components/DiffEditorMonaco.tsx b/website/src/components/DiffEditorMonaco.tsx index a88cf26abc91..30502db1d6ae 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,31 @@ 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; + 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(); + // Both panes lay out together; disposed along with the pane + panes[1].onDidLayoutChange(updateOffsets); + }, + [handleMount], + ); + return ( {() => { @@ -41,13 +69,25 @@ export default function DiffEditor({ documents, fallback }: DiffMonacoProps) { )} >
+
+ {documents.map(({ title }, i) => ( + + {title || (i ? 'After' : 'Before')} + + ))} +
Response - {status} + = 400, + })} + > + {STATUS_TEXT[status] ? `${status} ${STATUS_TEXT[status]}` : status} +
{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..612b450ee2de 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; } @@ -55,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 { @@ -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,30 @@ padding: 0.75rem var(--ifm-pre-padding); } -body { - --ifm-color-playground-tab-selected: var(--ifm-color-emphasis-300); +/* Playground chrome. Every header (editor tabs, Live Preview, Store) shares + 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; + --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; + --pg-tab-selected-bg: var(--monoco-code-background); +} +/* EndpointPlayground's right column is code, so it always uses the dark bar */ +.endpointPlayground { + --pg-bar: hsl(229, 8%, 26%); + --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%); } -div.playgroundTextEdit > .playgroundHeader { - background: var(--ifm-color-emphasis-600); - color: var(--ifm-color-content-inverse); +.playgroundHeader.tabControls, +div.playgroundTextEdit > .playgroundHeader, +.endpointPlayground .playgroundHeader { + box-shadow: inset 0 -1px 0 var(--pg-bar-edge); } .playgroundContainer:not(.row) > div.playgroundTextEdit @@ -99,10 +116,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*/ @@ -142,6 +155,8 @@ div > .playgroundHeader.subtabs { .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); @@ -182,11 +197,12 @@ 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); - 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; @@ -194,7 +210,8 @@ div > .playgroundHeader.subtabs { } .debugToggle:hover { - background-color: var(--ifm-color-emphasis-300); + background-color: var(--pg-tab-hover-bg); + color: var(--pg-tab-hover); } .hidden { @@ -229,28 +246,30 @@ 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); } +/* Selected tab joins the code panel below; inset edge avoids layout shift */ .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; + background-color: var(--pg-tab-selected-bg); + color: var(--pg-tab-selected); + box-shadow: inset 0 2px 0 var(--ifm-color-primary); } .small .tab { padding: 0.4rem 0.75rem; } -.subtabs .tab:hover { - background-color: var(--ifm-color-emphasis-400); +/* 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-tab-hover-bg); +} +.subtabs .tab:not(.selected):hover { + background-color: var(--pg-bar); +} +.controlTabs { + --pg-tab-selected-bg: var(--pg-tab-hover-bg); + --pg-tab-selected: var(--pg-tab-hover); } :root .playgroundPreview { @@ -267,7 +286,39 @@ div > .playgroundHeader.subtabs { margin-right: 8px; padding: 2px 6px; } -.playgroundPreview form input[type="text"] { +/* 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:where(:not([class])) { + padding: 0.3rem 0.8rem; + font-size: 0.9rem; + font-weight: 500; + line-height: 1.4; + cursor: pointer; +} +.playgroundPreview button:where(:not([class])):hover { + border-color: var(--ifm-color-primary); + color: var(--ifm-color-primary); +} +.playgroundPreview input[type='text']:where(:not([class])), +.playgroundPreview textarea:where(:not([class])) { + padding: 0.3rem 0.5rem; +} +.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 +342,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 +482,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) { @@ -457,7 +507,43 @@ 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: 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; + letter-spacing: 0.04rem; +} +.endpointPlayground .playgroundHeader.tabControls { + padding: 0; +} +.endpointPlayground .playgroundHeader .tab { + height: 33px; + padding: 0 0.75rem; +} + +/* 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; +}