diff --git a/.circleci/config.yml b/.circleci/config.yml index 1920cc12f..428d15391 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,7 +84,7 @@ jobs: test_benchmark: executor: name: code-infra/mui-node-browser - playwright-img-version: 'v1.58.2-noble' + playwright-img-version: 'v1.59.1-noble' resource_class: medium steps: - checkout diff --git a/AGENTS.md b/AGENTS.md index 7992294e9..d0a07942e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,8 @@ Always reference these instructions first and fallback to search or bash command - **Formatting**: `pnpm prettier` -- always run before pushing code. - **Run tests**: `pnpm test --run` takes 5-10 seconds. **NEVER CANCEL**. Set timeout to 30+ minutes. - **Run specific tests**: `pnpm test --run loadServerSource` or `pnpm test --run integration.test.ts` for targeted testing +- **Run browser tests**: `pnpm test:browser --run` -- requires podman or docker. Starts a containerized Playwright server and runs browser tests against it. +- **Run browser tests in CI**: In a `mcr.microsoft.com/playwright` container image, run `pnpm test:browser:unconfined` directly — no container engine needed. Only use in a CI environment. - **ALWAYS use `--run` flag** to avoid watch mode when running tests programmatically - **Do NOT use `--`** in test commands (e.g., avoid `pnpm test -- --run`) - **Use VS Code Vitest extension** whenever possible for interactive test development and debugging diff --git a/docs/app/bench/docs-infra/components/code-controller-context/demos/code/index.ts b/docs/app/bench/docs-infra/components/code-controller-context/demos/code/index.ts new file mode 100644 index 000000000..1ae19bf25 --- /dev/null +++ b/docs/app/bench/docs-infra/components/code-controller-context/demos/code/index.ts @@ -0,0 +1,4 @@ +import { createDemoPerformance } from '@/functions/createDemoPerformance'; +import Page from './page'; + +export const DemoCodeControllerContextPerformance = createDemoPerformance(import.meta.url, Page); diff --git a/docs/app/bench/docs-infra/components/code-controller-context/demos/code/page.tsx b/docs/app/bench/docs-infra/components/code-controller-context/demos/code/page.tsx new file mode 100644 index 000000000..6891f8544 --- /dev/null +++ b/docs/app/bench/docs-infra/components/code-controller-context/demos/code/page.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { createParseSource } from '@mui/internal-docs-infra/pipeline/parseSource'; +import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter'; +import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider'; +import { CodeController } from '../../../../../../docs-infra/components/code-controller-context/demos/code-editor/CodeController'; +import { CodeEditorContent } from '../../../../../../docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent'; + +import code from '../../../code-highlighter/snippets/large/snippet'; + +const sourceParser = createParseSource(); + +export default function Page() { + return ( + // @focus-start + + + + {code} + + + + // @focus-end + ); +} diff --git a/docs/app/bench/docs-infra/components/code-controller-context/page.mdx b/docs/app/bench/docs-infra/components/code-controller-context/page.mdx new file mode 100644 index 000000000..b92290c60 --- /dev/null +++ b/docs/app/bench/docs-infra/components/code-controller-context/page.mdx @@ -0,0 +1,10 @@ +# Benchmarking Code Controller Context + +This demo showcases the performance of the [`CodeControllerContext`](../../../../docs-infra/components/code-controller-context/page.mdx) component when handling large code snippets. +It uses RSC to highlight the initial code, and then highlights updates on the client side as the code is edited. + +import { DemoCodeControllerContextPerformance } from './demos/code'; + + + +[See Setup](./demos/code/) diff --git a/docs/app/docs-infra/components/code-controller-context/bench/page.mdx b/docs/app/docs-infra/components/code-controller-context/bench/page.mdx new file mode 100644 index 000000000..be8d75bd0 --- /dev/null +++ b/docs/app/docs-infra/components/code-controller-context/bench/page.mdx @@ -0,0 +1,5 @@ +import BenchCodeControllerContextPage from '@/app/bench/docs-infra/components/code-controller-context/page.mdx'; + +export default BenchCodeControllerContextPage; + +[See Benchmarks](/docs/app/bench/docs-infra/components/code-controller-context/page.mdx) diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx index 634f44db1..66bfdb1ef 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx @@ -1,7 +1,6 @@ 'use client'; import * as React from 'react'; -import { useEditable } from 'use-editable'; import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types'; import { useCode } from '@mui/internal-docs-infra/useCode'; import { LabeledSwitch } from '@/components/LabeledSwitch'; @@ -10,9 +9,7 @@ import styles from './CodeEditorContent.module.css'; import '../../../code-highlighter/demos/syntax.css'; export function CodeEditorContent(props: ContentProps) { - // @focus-start @padding 1 - const preRef = React.useRef(null); - const code = useCode(props, { preClassName: styles.codeBlock, preRef }); + const code = useCode(props, { preClassName: styles.codeBlock }); const hasJsTransform = code.availableTransforms.includes('js'); const isJsSelected = code.selectedTransform === 'js'; @@ -24,15 +21,6 @@ export function CodeEditorContent(props: ContentProps) { [code], ); - const onInput = React.useCallback( - (text: string) => { - code.setSource?.(text); - }, - [code], - ); - - useEditable(preRef, onInput, { indentation: 2 }); - return (
diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx index bdeaa435b..5f81f8c14 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx @@ -1,15 +1,12 @@ import * as React from 'react'; import { CodeProvider } from '@mui/internal-docs-infra/CodeProvider'; -import { DemoController } from './DemoController'; import { DemoCheckboxBasic } from './demo-basic'; export function DemoLive() { return ( // @focus-start - - - + // @focus-end ); diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css index 47c2764bc..f9dd0d739 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css @@ -85,3 +85,8 @@ .codeBlock:focus-visible { outline: none; } + +.codeBlock :global(.frame)[data-frame-type='highlighted'] { + background: #ebe4ff; + border-radius: 8px; +} diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx index b6a55998f..62d5339e2 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx @@ -1,7 +1,6 @@ 'use client'; import * as React from 'react'; -import { useEditable } from 'use-editable'; import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types'; import { useDemo } from '@mui/internal-docs-infra/useDemo'; import { LabeledSwitch } from '@/components/LabeledSwitch'; @@ -16,9 +15,7 @@ const variantNames: Record = { }; export function DemoLiveContent(props: ContentProps) { - // @focus-start @padding 1 - const preRef = React.useRef(null); - const demo = useDemo(props, { preClassName: styles.codeBlock, preRef }); + const demo = useDemo(props, { preClassName: styles.codeBlock }); const hasJsTransform = demo.availableTransforms.includes('js'); const isJsSelected = demo.selectedTransform === 'js'; @@ -41,14 +38,6 @@ export function DemoLiveContent(props: ContentProps) { [demo.variants], ); - const onChange = React.useCallback( - (text: string) => { - demo.setSource?.(text); - }, - [demo], - ); - useEditable(preRef, onChange, { indentation: 2, disabled: !demo.setSource }); - return (
{demo.component}
diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/createDemoClient.ts b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/createDemoClient.ts index 74c12378e..a0761d9f9 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/createDemoClient.ts +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/createDemoClient.ts @@ -1,6 +1,7 @@ 'use client'; import { createDemoClientFactory } from '@mui/internal-docs-infra/abstractCreateDemoClient'; +import { DemoController } from './DemoController'; /** * Creates a demo client copying dependencies in the client bundle for live editing. @@ -8,5 +9,5 @@ import { createDemoClientFactory } from '@mui/internal-docs-infra/abstractCreate * @param meta Additional meta and modules for the demo client. */ export const createDemoClient = createDemoClientFactory({ - live: true, + DemoController, }); diff --git a/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileContent.tsx b/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileContent.tsx index 6b8ebef9b..d980da1ed 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileContent.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileContent.tsx @@ -1,7 +1,6 @@ 'use client'; import * as React from 'react'; -import { useEditable } from 'use-editable'; import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types'; import { useCode } from '@mui/internal-docs-infra/useCode'; import { Tabs } from '@/components/Tabs'; @@ -10,9 +9,7 @@ import styles from '../code-editor/CodeEditorContent.module.css'; import '../../../code-highlighter/demos/syntax.css'; export function MultiFileContent(props: ContentProps) { - // @focus-start @padding 1 - const preRef = React.useRef(null); - const code = useCode(props, { preClassName: styles.codeBlock, preRef }); + const code = useCode(props, { preClassName: styles.codeBlock }); const tabs = React.useMemo(() => { return code.files.map(({ name }) => ({ @@ -21,15 +18,6 @@ export function MultiFileContent(props: ContentProps) { })); }, [code.files]); - const onInput = React.useCallback( - (text: string) => { - code.setSource?.(text); - }, - [code], - ); - - useEditable(preRef, onInput, { indentation: 2 }); - return (
diff --git a/docs/app/docs-infra/components/code-controller-context/page.mdx b/docs/app/docs-infra/components/code-controller-context/page.mdx index 8d973aeb2..912cd1393 100644 --- a/docs/app/docs-infra/components/code-controller-context/page.mdx +++ b/docs/app/docs-infra/components/code-controller-context/page.mdx @@ -137,22 +137,10 @@ The [`useCode`](../../hooks/use-code/page.mdx) hook automatically integrates wit ```tsx 'use client'; -import { useEditable } from 'use-editable'; import { useCode } from '@mui/internal-docs-infra/useCode'; function EditorContent(props) { - const preRef = React.useRef(null); - const code = useCode(props, { preRef }); - - // code.setSource updates the controller automatically - const onInput = React.useCallback( - (text: string) => { - code.setSource?.(text); - }, - [code], - ); - - useEditable(preRef, onInput, { indentation: 2 }); + const code = useCode(props); return (
@@ -165,7 +153,8 @@ function EditorContent(props) { ## Working with useDemo Hook -The [`useDemo`](../../hooks/use-demo/page.mdx) hook provides additional functionality for live component demos: +The [`useDemo`](../../hooks/use-demo/page.mdx) hook provides additional functionality for live component demos. +Editing is handled automatically by the `Pre` component — just render `demo.selectedFile`: ```tsx 'use client'; @@ -175,19 +164,12 @@ import { useDemo } from '@mui/internal-docs-infra/useDemo'; function DemoContent(props) { const demo = useDemo(props); - const onInput = React.useCallback( - (text: string) => { - demo.setSource?.(text); - }, - [demo], - ); - return (
{/* Live component preview */}
{demo.component}
- {/* Editable source code */} + {/* Editable source code — editing is handled automatically */}
-