Skip to content

feat(docs): build the composition pipeline and a plain Gallery - #269

Open
hunterbecton wants to merge 14 commits into
mainfrom
t3/implement-p-sha-2
Open

hunterbecton wants to merge 14 commits into
mainfrom
t3/implement-p-sha-2

Conversation

@hunterbecton

Copy link
Copy Markdown
Member

Why

ADR 0005 makes compositions the lead of the docs site, and nothing in the repo could hold one yet. This builds M1's composition pipeline end to end on a fixture, MeshGradient under Grain, which composition 1's assembly ticket replaces.

What changes

The composition document

A composition is compositions/<slug>/composition.ts, passed to defineComposition. Its types reject an unknown component or prop, a callback or signal, a preset or control that names a missing entry, and a control path that doesn't fit its kind. Documents import only types, so server code and the Node vitest read the registry. CompositionScene draws any composition.

The composition page

/gallery/<slug> shows the scene beside a Customize panel: a preset picker, curated controls, All props, and Copy React. The store starts from each component's library defaults, which props.ts now returns as values, and Reset returns to the loaded preset. All props mounts each demo's controls, now exported as <Component>Fields, under the entry's id. GodRays and RadialWipe now edit their real center prop instead of centerX and centerY, which also fixes their Copy React output.

The copied file

compositions/generate.ts prints the panel's state as a component file in the repo's Prettier style without running Prettier. Tests hold it to Prettier across every component's defaults. golden.test.ts commits the file for each preset, so CI typechecks, lints, and format-checks the code a visitor copies.

Checks and the Gallery

The fixture's visual spec asserts WebGL2, then captures each preset. /dev/composition-benchmark times a composition at hero size. At 2x on the M1 Max, under WebGPU, the fixture reads 1.48 ms for Default, 1.49 ms for Tight, and 1.38 ms for Coarse. /gallery is a plain poster grid that plays a card on hover, through the favorites' hover logic, now in lib/use-live-on-hover.ts. Reference pages get a provisional "Used in" strip.

Known limitations

  • All props shows each entry as a collapsible row, because Section can't collapse.
  • An entry whose position is 'cursor' shows a note in All props instead of its fields. A curated control on that position's coordinates throws.
  • The benchmark readings came from Playwright's Chromium with WebGPU on Metal, not from Chrome.
  • The "Used in" strip waits on your call to keep, redesign, or drop it.

Closes SHA-293
Closes SHA-294
Closes SHA-295
Closes SHA-296
Closes SHA-297
Closes SHA-298
Refs SHA-299

Copilot AI balanced review requested due to automatic review settings October 30, 2026 11:57
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 9326950.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Valid inputs can crash All props controls, FractalNoise preset/reset values can be overwritten and exported code can differ from previews, while invalid enum-backed color controls pass type checks.

5 open issues
What changed in this PR

Builds the docs site’s composition pipeline around a temporary MeshGradient-and-Grain fixture, supporting ADR 0005’s composition-led direction.

Changes:

  • Adds typed composition documents, preset state, shared rendering, and Copy React generation with golden-file checks.
  • Adds Gallery pages, reusable customization fields, hover previews, and a provisional “Used in” strip.
  • Extends poster generation, visual coverage, and manual GPU benchmarking for compositions.
File Description
scripts/​build-posters.sh Generates composition posters and thumbnails.
docs/​agents/​docs-site.md Documents composition authoring and verification.
apps/​docs/​src/​lib/​VisualTestPause.tsx Supports repeated captures and backend reporting.
apps/​docs/​src/​lib/​use-live-on-hover.ts Shares card engagement and scene-loading logic.
apps/​docs/​src/​content/​props.ts Parses library defaults into values.
apps/​docs/​src/​content/​props.test.ts Tests default-value parsing.
apps/​docs/​src/​content/​catalog.ts Maps component export names to slugs.
apps/​docs/​src/​content/​catalog.test.ts Tests component-name lookup.
apps/​docs/​src/​compositions/​store.ts Manages composition presets and editable state.
apps/​docs/​src/​compositions/​store.test.ts Covers presets, resets, and field seeding.
apps/​docs/​src/​compositions/​read-defaults.ts Reads defaults for stack entries.
apps/​docs/​src/​compositions/​posters.ts Defines composition image paths.
apps/​docs/​src/​compositions/​index.ts Registers compositions and usage lookups.
apps/​docs/​src/​compositions/​index.test.ts Checks registry uniqueness and images.
apps/​docs/​src/​compositions/​golden.test.ts Snapshots copied files for each preset.
apps/​docs/​src/​compositions/​generate.ts Generates copyable React component files.
apps/​docs/​src/​compositions/​generate.test.ts Tests serialization and formatting.
apps/​docs/​src/​compositions/​fixture/​generated/​tight.tsx Records the Tight preset’s generated output.
apps/​docs/​src/​compositions/​fixture/​generated/​default.tsx Records the Default preset’s generated output.
apps/​docs/​src/​compositions/​fixture/​generated/​coarse.tsx Records the Coarse preset’s generated output.
apps/​docs/​src/​compositions/​fixture/​composition.ts Defines the temporary fixture composition.
apps/​docs/​src/​compositions/​define.ts Defines typed documents, presets, and controls.
apps/​docs/​src/​compositions/​define.test.ts Checks document type constraints.
apps/​docs/​src/​compositions/​composition-scene.tsx Renders composition stacks through ShaderScene.
apps/​docs/​src/​compositions/​component-map.ts Maps public components for stack rendering.
apps/​docs/​src/​compositions/​component-map.test.ts Checks component-map completeness.
apps/​docs/​src/​components/​used-in/​used-in.tsx Links reference pages to relevant compositions.
apps/​docs/​src/​components/​used-in/​used-in.module.css Styles the provisional usage strip.
apps/​docs/​src/​components/​page-actions/​page-actions.tsx Supports copying complete composition files.
apps/​docs/​src/​components/​page-actions/​page-actions.module.css Styles the standalone copy action.
apps/​docs/​src/​components/​favorites/​favorites-list.tsx Adopts shared hover behavior.
apps/​docs/​src/​components/​favorites/​favorite-scenes.tsx Adopts shared scene loading.
apps/​docs/​src/​components/​controls/​store.ts Adds state loading with a new reset baseline.
apps/​docs/​src/​components/​controls/​SelectInput.tsx Extracts a reusable controlled select.
apps/​docs/​src/​components/​controls/​README.md Documents reusable field exports.
apps/​docs/​src/​components/​controls/​ControlPanel.tsx Supports configurable, accessible panel titles.
apps/​docs/​src/​app/​gallery/​page.tsx Adds the Gallery index.
apps/​docs/​src/​app/​gallery/​page.module.css Styles Gallery cards and previews.
apps/​docs/​src/​app/​gallery/​layout.tsx Adds the Gallery’s shared page frame.
apps/​docs/​src/​app/​gallery/​gallery-grid.tsx Displays posters with live hover previews.
apps/​docs/​src/​app/​gallery/​[slug]/​use-loaded-preset.ts Subscribes to the loaded preset name.
apps/​docs/​src/​app/​gallery/​[slug]/​page.tsx Builds composition pages from the registry.
apps/​docs/​src/​app/​gallery/​[slug]/​customize-panel.tsx Adds presets, controls, and Copy React.
apps/​docs/​src/​app/​gallery/​[slug]/​customize-panel.module.css Styles customization-specific elements.
apps/​docs/​src/​app/​gallery/​[slug]/​composition-island.tsx Connects preview and controls to one store.
apps/​docs/​src/​app/​dev/​composition-benchmark/​page.dev.tsx Adds the client-only benchmark route.
apps/​docs/​src/​app/​dev/​composition-benchmark/​benchmark-scene.tsx Benchmarks compositions at hero dimensions.
apps/​docs/​src/​app/​components/​wave-lines/​demo.tsx Exports reusable WaveLines fields.
apps/​docs/​src/​app/​components/​voronoi/​demo.tsx Exports reusable Voronoi fields.
apps/​docs/​src/​app/​components/​vignette/​demo.tsx Exports reusable Vignette fields.
apps/​docs/​src/​app/​components/​simplex-noise/​demo.tsx Exports reusable SimplexNoise fields.
apps/​docs/​src/​app/​components/​radial-wipe/​scene.tsx Passes the real center tuple.
apps/​docs/​src/​app/​components/​radial-wipe/​params.ts Replaces separate coordinates with center.
apps/​docs/​src/​app/​components/​radial-wipe/​demo.tsx Exports fields using center-coordinate paths.
apps/​docs/​src/​app/​components/​radial-gradient/​demo.tsx Exports reusable RadialGradient fields.
apps/​docs/​src/​app/​components/​mesh-gradient/​demo.tsx Exports reusable MeshGradient fields.
apps/​docs/​src/​app/​components/​liquid-gradient/​demo.tsx Exports reusable LiquidGradient fields.
apps/​docs/​src/​app/​components/​linear-gradient/​demo.tsx Exports reusable LinearGradient fields.
apps/​docs/​src/​app/​components/​led-wall/​demo.tsx Exports reusable LedWall fields.
apps/​docs/​src/​app/​components/​grain/​demo.tsx Exports reusable Grain fields.
apps/​docs/​src/​app/​components/​god-rays/​scene.tsx Passes the real center tuple.
apps/​docs/​src/​app/​components/​god-rays/​params.ts Replaces separate coordinates with center.
apps/​docs/​src/​app/​components/​god-rays/​demo.tsx Exports fields using center-coordinate paths.
apps/​docs/​src/​app/​components/​fractal-noise/​style-dials.ts Seeds style-dependent dial values.
apps/​docs/​src/​app/​components/​fractal-noise/​demo.tsx Exports fields with style-dial synchronization.
apps/​docs/​src/​app/​components/​fluted-glass/​demo.tsx Exports reusable FlutedGlass fields.
apps/​docs/​src/​app/​components/​dot-field/​demo.tsx Exports reusable DotField fields.
apps/​docs/​src/​app/​components/​dither/​demo.tsx Exports reusable Dither fields.
apps/​docs/​src/​app/​components/​dissolve/​demo.tsx Exports reusable Dissolve fields.
apps/​docs/​src/​app/​components/​demo-registry.tsx Registers reusable fields and seed functions.
apps/​docs/​src/​app/​components/​demo-fields.test.ts Checks field paths and stop-position registration.
apps/​docs/​src/​app/​components/​cursor-spotlight/​demo.tsx Exports reusable CursorSpotlight fields.
apps/​docs/​src/​app/​components/​cursor-smudge/​demo.tsx Exports reusable CursorSmudge fields.
apps/​docs/​src/​app/​components/​cursor-ripple/​demo.tsx Exports reusable CursorRipple fields.
apps/​docs/​src/​app/​components/​conic-gradient/​demo.tsx Exports reusable ConicGradient fields.
apps/​docs/​src/​app/​components/​blobs/​demo.tsx Exports reusable Blobs fields.
apps/​docs/​src/​app/​components/​aurora/​demo.tsx Exports reusable Aurora fields.
apps/​docs/​src/​app/​components/​[slug]/​page.tsx Adds composition links and updates copy targeting.
apps/​docs-tests/​visual/​gallery-fixture.spec.ts Captures each fixture preset on WebGL2.
apps/​docs-tests/​docs/​gallery-index.spec.ts Checks Gallery links, metadata, and posters.
.fallowrc.json Excludes generated fixtures from unused-code analysis.

Review effort: Balanced

Comment thread apps/docs/src/compositions/define.ts Resolved
Comment thread apps/docs/src/compositions/generate.ts Resolved
Copilot AI balanced review requested due to automatic review settings October 30, 2026 13:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The curated select can write strings into a numeric slider value, and incomplete escaping of nested multiline colors causes Copy React to produce invalid TSX.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Medium severity Escape multiline colors when serializing nested JavaScript strings

apps/​docs/​src/​compositions/​generate.ts:368

A valid multiline color such as oklch(0.5\n 0.1 250) renders correctly, but when used inside stops or palettes, this serializer embeds the actual newline inside a quoted JavaScript string. Copy React then produces invalid TSX. Escape line terminators before quoting, and escape backslashes in both quote branches; a nested multiline-color round-trip test would catch this.

5 resolved since last review

Review effort: Balanced

Comment thread apps/docs/src/compositions/define.ts Resolved
Copilot AI balanced review requested due to automatic review settings October 30, 2026 13:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Curated controls reject valid mixed-union numeric properties and can crash with dotted entry IDs, unnormalized stop positions, or missing stops.

No open issues 🎉

4 previously missed issues

In code that hasn't changed since last review

Medium severity Preserve dotted entry IDs when building control paths

apps/​docs/​src/​app/​gallery/​[slug]/​customize-panel.tsx:105

Entry ids are unrestricted strings, but concatenating an id into a dotted path changes its meaning. For example, an entry with id: 'background.mesh' is stored under that single key, while its Speed control tries to traverse background, then mesh, and throws during rendering. Pass an explicit segment array so the entry id stays intact, as All props already does through PathPrefixProvider.

Medium severity Guard curated color controls for missing stops

apps/​docs/​src/​app/​gallery/​[slug]/​customize-panel.tsx:131

A curated color control at stops.2.color crashes when All props removes the third Voronoi stop, because ColorInput reads a path that no longer exists. Loading a preset with fewer stops has the same effect. Reactively check target availability before mounting the input, and hide or disable the control while its target is absent.

Medium severity Support numeric controls for mixed-union crossSection values

apps/​docs/​src/​compositions/​define.ts:146

A curated slider or number control on FlutedGlass's crossSection is rejected: its path value is number | 'prism' | 'round' | 'flat', so the whole tuple fails this Extract against [string, number]. With selects now excluded too, this prop cannot have any curated control, despite being seeded to a numeric dial. Match the numeric member of mixed unions and add a positive type test for a crossSection slider.

Medium severity Normalize stop positions before rendering curated controls

apps/​docs/​src/​compositions/​store.ts:53

A type-valid curated slider on SimplexNoise's stops.1.position crashes on initial render because the default stops omit positions and this gate skips their normalization. FractalNoise, Blobs, and Voronoi have the same default shape. Resolve stop positions when curated controls need them too, and keep the generator's default comparison and normalization tests aligned.

1 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 30, 2026 13:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because golden tests can verify different output than the page copies, while numeric-union typing rejects a supported FlutedGlass slider configuration.

No open issues 🎉

Review effort: Balanced

A composition is a typed document that names each stack entry's component by export name and checks its literal props against that component. A client-only component map and one renderer draw any composition, and the fixture renders on /gallery/fixture. Refs SHA-293.
The composition page's store starts from each component's library defaults, which props.ts now returns as values, with the stack's props on top. A preset picker loads a preset from those defaults, and Reset returns to the loaded preset. ControlPanel takes a title, so the page names its panel Customize. Refs SHA-294.
A pure generator prints the panel's state as a component file in the repo's Prettier style, without running Prettier. Its tests run Prettier over every component's defaults to hold the line breaking to Prettier's. A golden-file test commits each preset's output, so CI compiles, lints, and format-checks the exact file a visitor copies. Copy React sits at the foot of the Customize panel and in the page header, which drops its markdown menu on composition pages. Refs SHA-295.
Every demo exports its controls as <Component>Fields, and the Customize panel mounts them under each stack entry's id in a collapsed section. GodRays and RadialWipe now edit their real center prop rather than demo-only centerX and centerY, which also fixes their Copy React output. The store writes out the stop positions the library spaces evenly, and FractalNoise seeds its style dials, so every field has a value to show. A test reads each demo's source and checks that its fields write only the component's own props. Refs SHA-296.
…position benchmark

VisualTestPause records the renderer's backend beside its ready flag, and remounts on every preset a composition page loads, so the fixture's spec asserts WebGL2 before it captures each preset through the picker. The fixture's Still preset gave the same first frame as Default, so Tight replaces it. /dev/composition-benchmark times any composition and preset at the hero's size against the Gallery's GPU budget. Refs SHA-297.
/gallery shows each composition as a card with its poster, title, and description. A card plays live on hover or keyboard focus through the hover logic the homepage favorites had, which moves into lib/use-live-on-hover.ts so both grids share it. scripts/build-posters.sh captures each composition's poster from its Default golden file, and the composition page shows it before the first frame. Refs SHA-298.
A component's reference page lists the compositions whose stack includes it, as one row of thumbnails linking to their Gallery pages, between the demo and Usage. It is a small server component that renders nothing when no composition uses the component, so it is easy to redesign or drop once the author has judged it. Refs SHA-299.
…ointer

A demo's fields edit a position as a point, so reading center.0 from 'cursor' threw and took down the composition island. The entry now shows a note in its place. The commit also folds in the review's tidy-ups: one name for the per-entry props map, one hook for the loaded preset, presetNames and isRecord in one place, shorter file summaries, a test that keeps POSITIONED_STOP_COMPONENTS in step with the demos, and docs that name the new pieces.
FlutedGlass's named cross-section and WaveLines' single or omitted line
colors now seed into the shapes their fields edit. FractalNoise writes
its style's dials only when a visitor picks a style, so a preset load or
Reset keeps the dials it sets. Copy React compares each prop against
what the component renders without it, through the store's seed, so a
contrast that matched the old style prints after a style change. Color
controls no longer accept a string-union path such as Grain's blend.
A select control now needs a prop whose whole value is a string-literal
union, so it can't write a name into FlutedGlass's numeric crossSection.
Copy React escapes line breaks in nested strings, and backslashes in
double-quoted ones, so a multiline color stays valid TSX.
A curated control now sits under its entry id as one path segment, so an
id with a dot in it stays one key. It mounts only while its target
exists, so removing a stop in All props, or loading a preset with fewer,
no longer crashes the panel.
Copilot AI balanced review requested due to automatic review settings October 31, 2026 12:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved issues can produce duplicate-binding syntax errors in generated React code, make golden snapshots diverge from copied output, and prevent supported numeric controls from compiling.

3 open issues

Review effort: Balanced

Comment thread apps/docs/src/compositions/define.ts Resolved
Comment thread apps/docs/src/compositions/generate.ts Resolved
Copilot AI balanced review requested due to automatic review settings October 31, 2026 19:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because an unpublished finding shows that allowed document titles can generate invalid React function names, leaving copied code and golden files uncompilable.

1 open issue
2 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 1, 2026 01:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because valid document titles can generate invalid React function identifiers, making copied output unusable.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants