Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ jobs:
ESMODULE_RELEVANT=false
fi
# Docs/website/tooling-only changes can't affect lint, typecheck or
# unit tests (Playground has unit tests, so it stays relevant).
# unit tests (Playground and motion have unit tests, so they stay relevant).
if ! grep -Evq "^(${DOCS_ONLY})" /tmp/ci-changed-files \
&& ! grep -q '^website/src/components/Playground/' /tmp/ci-changed-files; then
&& ! grep -Eq '^website/src/components/(Playground|motion)/' /tmp/ci-changed-files; then
echo "Only docs/website/tooling paths changed; test jobs will halt."
TESTS_RELEVANT=false
fi
Expand Down Expand Up @@ -184,8 +184,9 @@ jobs:
- project/node_modules
- project/packages
- project/scripts
# Playground unit tests (transformCode, codeModel); rest of website omitted
# Playground and motion unit tests; rest of website omitted
- project/website/src/components/Playground
- project/website/src/components/motion
Comment thread
ntucker marked this conversation as resolved.
- project/.yarnrc.yml
- project/babel.config.js
- project/eslint.config.mjs
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/ci-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ paths:
- Jest `--maxWorkers` is pinned per job to the `resource_class` vCPU count (large = 4, medium = 2) because docker containers report the host's CPUs via `os.cpus()`. Exception: the ReactNative `unit_tests` run is deliberately uncapped — its suites are fake-timer-wait dominated and capping workers flakes 5s test timeouts.
- Jobs halt via the `halt-unless-relevant-change` command based on flags computed once in `setup` (`.ci-esmodule-relevant`, `.ci-tests-relevant`) and transported via `save_cache`/`restore_cache` (keyed on `CIRCLE_SHA1`) so jobs can halt before paying `attach_workspace`. Missing/unreadable flags fail open (jobs run). On the default branch both flags are always true (a push may carry several commits). The diff uses `--no-renames` so moving a file out of a relevant dir still counts.
- `esmodule` (validate-esmodule-browser-build, esmodule-types*): a denylist, so new paths fail open. False only when every changed path is provably outside the esmodule jobs' inputs: the shared `DOCS_ONLY` paths (also the `tests` denylist) plus `.vscode/`, `plans/`, root `__tests__/` (excluded by every `tsconfig.compile.json`), `eslint.config.mjs`, `jest.config.js`, `examples/*.md`, and examples the jobs never build (`benchmark`, `benchmark-react`, `coin-app`, `nextjs`, `normalizr-github`, `normalizr-redux`, `test-bundlesize`, `vue-todo-app`). Only add a path if no esmodule job (or the `setup` builds feeding them) reads it.
- `tests` (lint, typecheck, unit_tests, node_matrix): false only when every changed path is docs/website/tooling (`website/`, `docs/`, `.changeset/`, `.cursor/`, `.agents/`, `.claude/`, `.github/`, root `*.md`), except `website/src/components/Playground/` (has unit tests). When both flags are false, `setup` halts before install.
- `tests` (lint, typecheck, unit_tests, node_matrix): false only when every changed path is docs/website/tooling (`website/`, `docs/`, `.changeset/`, `.cursor/`, `.agents/`, `.claude/`, `.github/`, root `*.md`), except `website/src/components/Playground/` and `website/src/components/motion/` (have unit tests). When both flags are false, `setup` halts before install.
- Legacy TS types (`ci:build:legacy-types`, consumed by `esmodule-types`):
- Built inside `setup` (`ci:build:setup:esmodule`) only when the esmodule flag is set; there is no separate job, to keep a job hop off the critical path.
- CI builds the endpoint, normalizr and rest legacy outputs, all for TS >= 4.0 (the minimum supported TS, and the oldest in the `esmodule-types` matrix). `use-enhanced-reducer` still ships a `ts3.4` build in release builds (`build:types`).
Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/ci-config.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ alwaysApply: false
- Jest `--maxWorkers` is pinned per job to the `resource_class` vCPU count (large = 4, medium = 2) because docker containers report the host's CPUs via `os.cpus()`. Exception: the ReactNative `unit_tests` run is deliberately uncapped — its suites are fake-timer-wait dominated and capping workers flakes 5s test timeouts.
- Jobs halt via the `halt-unless-relevant-change` command based on flags computed once in `setup` (`.ci-esmodule-relevant`, `.ci-tests-relevant`) and transported via `save_cache`/`restore_cache` (keyed on `CIRCLE_SHA1`) so jobs can halt before paying `attach_workspace`. Missing/unreadable flags fail open (jobs run). On the default branch both flags are always true (a push may carry several commits). The diff uses `--no-renames` so moving a file out of a relevant dir still counts.
- `esmodule` (validate-esmodule-browser-build, esmodule-types*): a denylist, so new paths fail open. False only when every changed path is provably outside the esmodule jobs' inputs: the shared `DOCS_ONLY` paths (also the `tests` denylist) plus `.vscode/`, `plans/`, root `__tests__/` (excluded by every `tsconfig.compile.json`), `eslint.config.mjs`, `jest.config.js`, `examples/*.md`, and examples the jobs never build (`benchmark`, `benchmark-react`, `coin-app`, `nextjs`, `normalizr-github`, `normalizr-redux`, `test-bundlesize`, `vue-todo-app`). Only add a path if no esmodule job (or the `setup` builds feeding them) reads it.
- `tests` (lint, typecheck, unit_tests, node_matrix): false only when every changed path is docs/website/tooling (`website/`, `docs/`, `.changeset/`, `.cursor/`, `.agents/`, `.claude/`, `.github/`, root `*.md`), except `website/src/components/Playground/` (has unit tests). When both flags are false, `setup` halts before install.
- `tests` (lint, typecheck, unit_tests, node_matrix): false only when every changed path is docs/website/tooling (`website/`, `docs/`, `.changeset/`, `.cursor/`, `.agents/`, `.claude/`, `.github/`, root `*.md`), except `website/src/components/Playground/` and `website/src/components/motion/` (have unit tests). When both flags are false, `setup` halts before install.
- Legacy TS types (`ci:build:legacy-types`, consumed by `esmodule-types`):
- Built inside `setup` (`ci:build:setup:esmodule`) only when the esmodule flag is set; there is no separate job, to keep a job hop off the critical path.
- CI builds the endpoint, normalizr and rest legacy outputs, all for TS >= 4.0 (the minimum supported TS, and the oldest in the `esmodule-types` matrix). `use-enhanced-reducer` still ships a `ts3.4` build in release builds (`build:types`).
Expand Down
16 changes: 8 additions & 8 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ const packages = [
'test',
];

// CircleCI persist_to_workspace omits most of website/; only include this root
// when the tree is present (full checkout / when CI persists Playground).
const playgroundRoot = path.join(
__dirname,
// CircleCI persist_to_workspace omits most of website/; only include these
// roots when the tree is present (full checkout / when CI persists them).
const websiteRoots = [
'website/src/components/Playground',
);
'website/src/components/motion',
];
const reactDomRoots = [
...packages.map(pkgName => `<rootDir>/packages/${pkgName}/src`),
...(fs.existsSync(playgroundRoot) ?
['<rootDir>/website/src/components/Playground']
: []),
...websiteRoots
.filter(root => fs.existsSync(path.join(__dirname, root)))
.map(root => `<rootDir>/${root}`),
];

const projects = [
Expand Down
8 changes: 8 additions & 0 deletions website/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import path from 'path';
import { themes } from 'prism-react-renderer';

import gqlRedirects from './gqlRedirects';
import { motionCss } from './src/components/motion/css';
import versions from './versions.json';

// Keep Monaco CDN preload hashes in sync with the installed monaco-editor package.
Expand Down Expand Up @@ -299,6 +300,13 @@ const config: Config = {
],
],
plugins: [
// global motion styles (spring tokens, <Reveal>): website/src/components/motion/css.ts
() => ({
name: 'motion-css',
injectHtmlTags: () => ({
headTags: [{ tagName: 'style', innerHTML: motionCss() }],
}),
}),
[
'@docusaurus/plugin-content-docs',
{
Expand Down
5 changes: 4 additions & 1 deletion website/src/components/Playground/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ DesignSystem/ components injected into preview scope
- Each playground gets its own `DataProvider` store (`MockResolver` serves
`fixtures`); `memo(Preview)` keeps it from re-rendering on code edits.
- Store inspector open state persists per `groupId` via tab storage and
avoids scroll jumps; in `row` layout it replaces the result while open.
avoids scroll jumps; in `row` layout it covers the result while open (the
result stays rendered underneath, `inert`). It opens and closes as a
drawer (`../motion`: the toggle glides, the panel `Reveal`s); the panel's
contents render a frame after it starts moving (`useDeferredValue`).
- `renderCount` wraps the live result in a `<Profiler>` and shows its commit
count in the preview header (written to the DOM, so counting adds no commits).
`website/profiling-plugin.js` replaces `react-dom/client` with React's
Expand Down
30 changes: 17 additions & 13 deletions website/src/components/Playground/preview/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import React, {
type ProfilerOnRenderCallback,
} from 'react';

import { MotionGroup } from '../../motion';
import Boundary from '../Boundary';
import type { PreviewErrorProps } from './PreviewError';
import StoreInspector from './StoreInspector';
Expand Down Expand Up @@ -64,26 +65,29 @@ function Preview<T>({
[],
);

const hiddenResult = row && selectedValue === 'y';
const coveredResult = row && selectedValue === 'y';
return (
<DataProvider managers={managers} initialState={initialState}>
<MockResolver
fixtures={fixtures}
silenceMissing={true}
getInitialInterceptorData={getInitialInterceptorData}
>
<div
className={clsx('playground-preview', styles.playgroundPreview, {
[styles.hidden]: hiddenResult,
})}
onPointerDownCapture={onInteract}
onKeyDownCapture={onInteract}
>
<Boundary fallback={null}>
<PreviewBlockLazy onCommit={onCommit} {...errorProps} />
</Boundary>
</div>
<StoreInspector selectedValue={selectedValue} toggle={toggle} />
<MotionGroup layoutDependency={selectedValue}>
<div
className={clsx('playground-preview', styles.playgroundPreview, {
[styles.covered]: coveredResult,
})}
inert={coveredResult}
onPointerDownCapture={onInteract}
onKeyDownCapture={onInteract}
>
<Boundary fallback={null}>
<PreviewBlockLazy onCommit={onCommit} {...errorProps} />
</Boundary>
</div>
<StoreInspector selectedValue={selectedValue} toggle={toggle} />
</MotionGroup>
</MockResolver>
</DataProvider>
);
Expand Down
16 changes: 11 additions & 5 deletions website/src/components/Playground/preview/StoreInspector.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { StateContext } from '@data-client/react';
import clsx from 'clsx';
import React, { useContext, memo, useMemo } from 'react';
import React, { useContext, useDeferredValue, memo, useMemo } from 'react';

import { Reveal, useLayoutMotion } from '../../motion';
import styles from '../styles.module.css';
import Tree from './Tree';

Expand All @@ -13,12 +14,16 @@ function StoreInspector({
toggle: React.MouseEventHandler<HTMLDivElement>;
}) {
const isSelected = selectedValue === 'y';
// the empty drawer starts moving at once; the tree renders a frame later
const showTree = useDeferredValue(isSelected);
return (
<>
<StoreToggle onClick={toggle} open={isSelected} />
{isSelected ?
<StoreTreeM />
: null}
<Reveal show={isSelected} className={styles.storePanel}>
{showTree ?
<StoreTreeM />
: null}
</Reveal>
</>
);
}
Expand All @@ -32,8 +37,9 @@ export function StoreToggle({
onClick?: React.MouseEventHandler<HTMLDivElement>;
open?: boolean;
}) {
const ref = useLayoutMotion();
return (
<div className={styles.debugToggle} onClick={onClick}>
<div className={styles.debugToggle} onClick={onClick} ref={ref}>
Store
<span
className={clsx(
Expand Down
25 changes: 24 additions & 1 deletion website/src/components/Playground/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ div.playgroundTextEdit > .playgroundHeader,
}

.arrow {
transition: all 200ms ease 0s;
transition: transform var(--motion-snappy);
transform-origin: 45% 50% 0px;
position: relative;
display: inline-block;
Expand Down Expand Up @@ -267,6 +267,9 @@ div.playgroundTextEdit > .playgroundHeader,
.playgroundResult {
display: flex;
height: 100%;
/* frame for the Store drawer as it slides in and out */
position: relative;
isolation: isolate;
}

.debugToggle {
Expand All @@ -284,6 +287,26 @@ div.playgroundTextEdit > .playgroundHeader,
flex: 0 0 auto;
}

/* The Store drawer (painted before its contents render) */
.storePanel {
flex: 4 1 40%;
min-width: 0;
background: var(--monoco-code-background);
}
[data-theme='dark'] .storePanel {
/* opaque, so the covered result never shows through; the tree inside
adds the translucent code tint on top */
background: var(--ifm-background-color);
}

/* Row layout with the Store open: the result stays underneath, so the Store
slides over real content instead of an empty frame */
.covered {
position: absolute;
inset: 0;
z-index: -1;
}

.debugToggle:hover {
background-color: var(--pg-tab-hover-bg);
color: var(--pg-tab-hover);
Expand Down
Loading
Loading