You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(docs): build the composition pipeline and a plain Gallery - #269
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 todefineComposition. 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.CompositionScenedraws 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, whichprops.tsnow 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 realcenterprop instead ofcenterXandcenterY, which also fixes their Copy React output.The copied file
compositions/generate.tsprints 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.tscommits 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-benchmarktimes 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./galleryis a plain poster grid that plays a card on hover, through the favorites' hover logic, now inlib/use-live-on-hover.ts. Reference pages get a provisional "Used in" strip.Known limitations
Sectioncan't collapse.'cursor'shows a note in All props instead of its fields. A curated control on that position's coordinates throws.Closes SHA-293
Closes SHA-294
Closes SHA-295
Closes SHA-296
Closes SHA-297
Closes SHA-298
Refs SHA-299