Conversation
Keypoints, compute the palette on the serverKeypoints, compute the palette on the server
abernier
marked this pull request as draft
August 18, 2026 16:05
abernier
marked this pull request as ready for review
August 18, 2026 16:32
docs stops maintaining its own copy of the colour system. The ~60-line hand-written @theme mapping and the 33-line shadcn remap in globals.css are replaced by one registry item, pmndrs/design-system/md3, which carries both plus the config they map β so they can no longer drift from each other. react-mcu gives way to material-theme-builder, which is what that item installs. Mostly a rename: --mcu-* becomes --md-sys-color-*. The --color-* names in the @theme mapping are identical either way, so every bg-surface, bg-primary-container and text-on-surface-variant keeps working untouched. Two components referenced the raw variables rather than a utility β Code and Sandpack β and those are the only component edits. The seed moves to poimandres mint. It also stops being restated: it lived in the layout, in build.yml's input defaults and hard-coded again in ci.yml, three copies free to disagree. Now src/lib/md3.ts holds it and the workflow inputs default to empty, so a site overrides the seed by setting one and inherits the pmndrs default by leaving it alone. Also applies the poimandres preset. It is close to a no-op here by design: radius already matched, the colour half is overridden by the remap, and the font half is deliberately not applied because docs self-hosts Inter and Inconsolata. What it does land is the identity in components.json and a --sidebar* block in oklch rather than hsl. Verified: no token lost β all 67 --color-* names the old mapping defined are in the 191 the new one does β and a full build renders mint, with bg-surface-dim, bg-note-container and text-primary-fixed all emitting. Refs pmndrs/design-system#1 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDvnsrory1aJsoNALn5aLB
Follows pmndrs/design-system#3. note, tip, important, warning and caution are GitHub's palette, and this generator is the only thing rendering markdown alerts β they were never the design system's to carry. Tier 1 now ships the seed alone, at v0.2.0. src/lib/mtb.ts spreads pmndrsMtb and adds the five, so src/lib/md3.ts stays a verbatim copy of the installed item and updating it remains a clean overwrite. blend: true still harmonizes them against the seed, and THEME_NOTE and its four siblings still override them. Their twenty @theme lines move out of the shadcn-managed block into one of our own, commented, next to nothing else β the config and the mapping have to be read together, since a name declared in one and missing from the other makes Tailwind emit no rule at all, without an error. Refs pmndrs/design-system#1 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EDvnsrory1aJsoNALn5aLB
`src/lib/cn.ts` and `src/lib/utils.ts` were byte-equivalent implementations of the same helper, differing only in export style. `components.json` points its `utils` alias at the latter, so anything installed by `shadcn add` imports the named export while everything hand-written imported the default one. Keep the aliased file, drop the other. Prerequisite for extracting a block: a distributed component has to import through the alias the CLI rewrites.
First Tier 2 block of the pmndrs UI distribution, and the first thing to resolve a dependency across registries: `bg-surface-dim` has no shadcn equivalent, so the item declares `pmndrs/design-system/md3#v0.3.0` and the install pulls Tier 1 with it. That path is what the whole architecture rests on, and nothing else exercises it. The component moves to `registry/keypoints/`, at the root where shadcn looks for registry items, and this app imports it back through a `@/registry/*` alias β so what ships is the file the site renders, not a copy of it. Three changes were needed to make it standalone: - it pulled `ul` and `li` from this app's MDX component map. Both are trivial wrappers and no second block needs them yet, so they are inlined; the rule is to promote them to their own item at n=2. - `className` now merges after the defaults rather than before, so a consumer can override them, and `title` is optional in the type as it already was in practice. - `border-outline-variant` becomes `border-border`. The two are 1:1, and the stock token is the one a consumer who skipped Tier 1 still has. `bg-surface-dim` stays: it is the deliberate reach, and the reason for the dependency. `registry.json` is validated by `pnpm check-registry`. That validator never parses TSX, so it cannot see a missing token β an install into a scratch app outside this repo remains the only check that would.
β¦thout its bake `<Mtb>` carries `'use client'`, so this site shipped material-theme-builder's palette code to every visitor and recomputed identical values on each load. The package's root export holds `builder` alone and carries no directive, so the root layout calls it and emits `toCss()` itself. Nothing about the rendered result changes; the computation moves off the client. That costs ~2 kB brotli of repeated `<style>` per document β the declarations are highly compressible. A build step could hoist it into the stylesheet and save that, but not for the price: a generated file, a fifth npm script wrapping the other four, `allowImportingTsExtensions`, and an import-order rule that fails silently whenever the seed matches the default. Not worth it at this size. The design system now splits into `md3-base` β the Tailwind mapping, the shadcn remap and the seed β and `md3`, which composes a baked palette on top. Blocks depend on `md3` and get colours with nothing to mount. This site installs `md3-base`, because it computes its own palette: `docsMtb` adds the five alert colours, and `blend: true` derives them from whatever `THEME_PRIMARY` the deployment set, which an upstream bake cannot know. So `globals.css` is untouched by this branch, and `--md-sys-color-*` has exactly one definition rather than two. Storybook gets the palette from an `<Mtb>` decorator. It imports the stylesheet and renders no layout, so those variables were undefined there β and since the remap points the stock shadcn variables at MD3 roles, that took `--background`, `--primary` and `--border` down with it. Every story has been rendering colourless, and Chromatic baselining it. The client component the app avoids is the right tool here: Storybook is a browser, and there is no build to hook. `registryDependencies` for `keypoints` stays on `md3`, now at `#v0.3.0`, which needs the tag on pmndrs/design-system#4.
Upstream moved the pmndrs seed from the mint `#5de4c7` to the slate `#323e48`. `src/lib/md3.ts` is a verbatim copy of the installed item, so this is that overwrite, and the changeset's "visible change" line follows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmZpQG8qwLrA7V7RpKySJR
material-theme-builder v5 deletes `tailwind.css` and replaces it with `@plugin "material-theme-builder/tailwind"`. The plugin emits everything the stylesheet did β the same 115 `--color-*` names, same values β and adds the one thing a static stylesheet could not: it takes the custom colour names and maps them too. So the twenty hand-written `@theme inline` lines go. They existed because the old stylesheet covered standard M3 roles only, and each custom colour needed its four roles wired by hand; forgetting one emitted no rule and no error, which is what the comment above them warned about. Naming the five in the `@plugin` block does the same job in one line, and there is nothing left to forget β so the warning goes with them, in `src/lib/mtb.ts` as well. The plugin also gives each custom colour its eleven ref-palette shades (`bg-note-50` β¦ `bg-note-950`), which the stylesheet never offered. Nothing uses them yet. Verified in the running dev server rather than on a clean build, since a missing utility is exactly the failure that builds stay quiet about: the five alerts on /authoring/gha compute byte-identical backgrounds before and after, in both themes β dark `rgb(27,73,117)` / `rgb(0,82,52)` / `rgb(66,64,120)` / `rgb(85,69,0)` / `rgb(114,51,63)`, light `rgb(210,228,255)` / `rgb(171,242,201)` / `rgb(227,223,255)` / `rgb(251,225,134)` / `rgb(255,217,221)` β as do `bg-surface-dim` and `bg-primary`. `registryDependencies` moves to `pmndrs/design-system/md3#v0.4.0`, the tag that carries the `@plugin` item, and `src/lib/md3.ts` is re-synced from it β it stays a verbatim copy of the installed file, and its JSDoc carried the same stale four-lines-per-colour note. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SMiASXrLM2zzaNgMSdbDcu
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
PRs 2β4 of pmndrs/design-system#1, collapsed into one, and stacked on #572. This was a three-PR stack β #567, #568 and #570 β and it is now the same commits reviewable in one place. The two others are closed, not abandoned; their discussion threads stay where they are.
#572 carries the
react-mcuβmaterial-theme-builderswap on its own, so what is left here is only the question of where the colour layer lives β not which package computes it. Review #572 first.Three things happen, in order:
pmndrs/design-system.Keypointsmoves out ofsrc/components/mdx/and becomes a registry block anyone can install.1. Take the MD3 colour layer from pmndrs/design-system
#572 already took both CSS layers straight from
material-theme-builderβ@pluginfor the Tailwind mapping,@importfor the shadcn remap. So what this commit installs frompmndrs/design-systemis the seed and its config, and nothing else.globals.cssgains not one colour line.That is worth stating plainly, because it was not the original plan: the
md3registry item does carry a mapping and a remap of its own, and this branch used to vendor them. It no longer needs to. The item's CSS half is inert on this site β see Known, not fixed here.Mostly a rename:
--mcu-*becomes--md-sys-color-*. The--color-*names on top are identical either way, so everybg-surface,bg-primary-containerandtext-on-surface-variantin the codebase keeps working untouched.The plan said "no component changes". Not quite β two components reference the raw variables rather than a Tailwind utility,
Code(the prism fixed colour) andSandpack(its three surface levels). Those four lines are the only component edits.What the design system owns, and what stays here
The first pass took the five alert colours from the design system too. It shouldn't have β
note,tip,important,warningandcautionare GitHub's palette, and this generator is the only thing rendering markdown alerts. pmndrs/design-system#3 pulled them back out; upstream now ships the seed alone.So they live in
src/lib/mtb.ts, which spreadspmndrsMtband adds them. Spreading rather than editing keepssrc/lib/md3.tsa verbatim copy of the installed item, so updating it stays a clean overwrite.blend: truestill harmonizes them against the pmndrs seed, andTHEME_NOTEand its four siblings still override them.Their twenty
@themelines moved out of the shadcn-managed block into one of ours, commented. The config and the mapping have to be read together: a name declared in one and missing from the other makes Tailwind emit no rule at all, with no error β so they should not be a block apart with shadcn free to rewrite one of them.The seed
It moves to the pmndrs seed, the slate
#323e48. This is the one visible change β everything else is meant to render identically. (Upstream seeded on the mint#5de4c7when this branch was first written; pmndrs/design-system@f46f2a9 moved it to the slate, andsrc/lib/md3.tsβ a verbatim copy of the installed item β follows.)It also stops being restated. The seed lived in
layout.tsx, again inbuild.yml's input defaults, and hard-coded a third time inci.ymlβ three copies free to disagree, which is exactly what the plan flagged. Nowsrc/lib/md3.tsholds it, installed from the design system, and the workflow inputs default to empty. A site overrides the seed by setting one; a site that leaves it alone inherits the pmndrs default.THEME_PRIMARY,THEME_SCHEME,THEME_CONTRASTand the five alert colours behave as before.Worth a second look, since
build.ymlis reusable and other repos call it: the defaults it used to hand out are now the design system's rather than its own.The preset
apply b5cR4Y50S --only theme. Close to a no-op here, by design β radius already matched at0.625rem, the colour half is overridden by the remap on the next line, and the font half is deliberately left out because docs self-hosts Inter and Inconsolata. What it does land is the identity incomponents.jsonand a--sidebar*block inoklchinstead ofhsl.Two things to know if you re-run the CLI here.
applywrites the palette into the first:rootit finds, which here is the layout block holding--rgrid-mandscroll-behavior, not the shadcn one further down β which then wins, being later. I moved the values into the shadcn block and restored the layout one. Andaddmatches its:root, .darkblock on the exact selector string, which prettier reformats to:root,\n.darkon commit β so re-installing the item appends a second, identical copy. Worth a glance at the diff after either command.2. Extract
Keypointsinto a registry blockKeypointsbecomes the first block of the pmndrs UI distribution:It is also the first thing anywhere to resolve a dependency across registries.
bg-surface-dimis an MD3 surface role shadcn has no equivalent for, so the item declarespmndrs/design-system/md3#v0.3.0and oneaddpulls the shared colour layer down with it. That path is what the RFC's whole architecture rests on, and nothing else exercises it β which is most of why this component went first, rather than the nav.Nothing changes for authors. The same
<Keypoints>/<KeypointsItem>in the same MDX, rendering the same; only the source moved, fromsrc/components/mdx/Keypoints/toregistry/keypoints/, withregistry.jsonat the root next to it. The app imports it back through a new@/registry/*alias, so what ships to a consumer is the file this site renders rather than a copy that can drift from it.What being distributable cost
Three things in the component only worked because it lived inside this app.
import { ul as Ul, li as Li } from '../index'β which a consumer does not have. Both are trivial wrappers, and no second block needs them yet, so they are inlined. Per the plan's promotion rule that is the n=1 answer; the second block that wants them promotes them to their own registry item.cnthrough a pathcomponents.jsondoes not alias.src/lib/cn.tsandsrc/lib/utils.tswere byte-equivalent, differing only in export style, and theutilsalias β the one the CLI rewrites on install β pointed at the latter. One commit standardizes all 25 importers on it and deletes the duplicate.border-outline-variant, which isborder-borderby another name once the MD3 remap is in place. It now uses the stock token, so a consumer who installed the block but skipped the colour layer still gets a border.bg-surface-dimstays β that one is the deliberate reach, and the reason for the dependency.Two smaller corrections while the API was becoming public:
classNamenow merges after the defaults rather than before, so a consumer can actually override them, andtitleis optional in the type as it already was in practice.The pin is
v0.3.0, deliberatelyv0.2.0is the shared layer without its bake: the Tailwind mapping and the shadcn remap, pointing at--md-sys-color-*variables nothing defines unless the consumer mounts<Mtb>themselves. Install the block at that pin and the panel has no background at all β and since the remap points the stock variables at MD3 roles,--backgroundand--primarygo with it. Silently, as ever.v0.3.0bakes the palette into the CSS, soshadcn add pmndrs/docs/keypointsis enough on its own.It stays at
v0.3.0here, which is the mint bake β the slate is on the design system'smainand has no tag yet. This site is unaffected, since it installsmd3-baseand computes its own palette; what it means is that a consumer installing the block today gets a mint panel. The pin moves to the slate release once it is tagged.3. Compute the palette on the server
<Mtb>carries'use client', so this site shippedmaterial-theme-builder's palette code to every visitor and recomputed identical values on each load. The package's root export holdsbuilderalone and carries no directive, so the root layout calls it and emitstoCss()itself. Same rendered result, no palette code on the client.That leaves ~32 kB of repeated
<style>per document β but 3.4 kB gzip, 2.1 kB brotli: they're hex declarations, they compress about 15:1. An earlier version added a prebuild script to hoist that into the stylesheet, and it wasn't worth its price β a generated file, a fifth npm script wrapping the other four,allowImportingTsExtensions, a changed import style inmtb.ts, and an import-order rule that fails silently whenever the seed happens to match the default. Reverted. The 32 kB figure I first quoted was raw bytes and overstated the real cost by 15Γ.globals.cssis untouched by itUpstream splits the item:
md3-baseis the Tailwind mapping, the shadcn remap and the seed;md3composes a baked palette on top. Blocks depend onmd3and get colours with nothing to mount. That split is for consumers β on this site the first two thirds are redundant, since #572 takes them from the package directly.This site installs
md3-base, because it computes its own palette βdocsMtbadds the five alert colours, andblend: truederives them from whateverTHEME_PRIMARYthe deployment set, which an upstream bake cannot know. Soglobals.cssis byte-identical to what part 2 left, rather than carrying 252 generated declarations overridden on the next line, and--md-sys-color-*has exactly one definition instead of two. No cascade-ordering rule to get wrong either, since there is nothing to override. What the site actually takes frommd3-baseis the seed β the mapping and remap it also carries are already inglobals.cssfrom #572.Storybook has been rendering colourless
.storybook/preview.tsximports the stylesheet and renders no layout, so--md-sys-color-*was undefined there β it only ever existed at runtime, from<Mtb>in the app. And since the shadcn remap points the stock variables at MD3 roles, that took--background,--primaryand--borderdown with it. Chromatic has been baselining colourless components this whole time.Fixed with an
<Mtb>decorator. The client component the app deliberately avoids is the right tool here: Storybook is a browser, and there is no build to hook. Expect broad Chromatic changes β they are the fix, not a regression.Net effect on the boundary: this repo keeps
material-theme-builderbecause it is itself a multi-site generator, not because it consumes the design system. A site that just wants pmndrs colours installsmd3and runs none of it.Verified
Nothing in-repo can catch a missing token:
registry validatenever parses TSX, Tailwind emits no rule and no error for an unknown utility, and a story runs inside this repo where every token exists by construction. So the scratch-app install is a required step rather than a nicety.create-next-app,shadcn init --preset b5cR4Y50S, thenshadcn add pmndrs/docs/keypoints. It resolved the block, followed the dependency topmndrs/design-system/md3#v0.3.0, installedmaterial-theme-builderand the seed, and rendered a correctly-themed panel with nothing mounted. Every utility the block uses emits a rule in the built CSS β checked individually,bg-surface-dimincluded, which resolves tovar(--md-sys-color-surface-dim).--color-*names the old mapping defined are among the 191 the new one defines.bg-note-containerandbg-tip-containeremit rules, and--md-sys-color-{note,tip,important,warning,caution}-containerare all server-rendered.next build, then the running site: alerts render, seed is--md-ref-palette-primary-40: #086b5a, nav accent and Keypoints panel correct in dark mode, 69 sys-colour and 308 palette variables server-rendered. Re-checked on the running site after the seed moved to the slate:--md-ref-palette-primary-40: #266389, the same 69 and 308, and the five alert containers still resolving. No client chunk carries a palette variable, and no stylesheet chunk defines one β the<style>is the single source.THEME_PRIMARY=#ff5500rendered orange throughout, with the alert colours re-harmonized against the new seed.build-storybook, then the built story served statically: thebg-surface-dimpanel renders with a real colour.tscclean,eslint,prettier --check, 95 tests,pnpm check-registry(new script,shadcn registry validate) green.lintreports tworeact/no-unescaped-entitiesinsrc/stories/Page.tsx; both pre-date this branch, verified againstmain.Chromatic will show the two Keypoints stories as removed and re-added rather than changed β the story ID follows the file path, and the file moved. And expect the broad colour changes described in part 3: those are the fix.
Known, not fixed here
pmndrs/design-system/md3is two-thirds redundant here. Since Move the colour engine tomaterial-theme-builderΒ #572 takes the Tailwind mapping and the shadcn remap frommaterial-theme-builderitself, the only part of the item this site uses is the seed. That is fine for a consumer β a third-party block installs one item and gets everything β but it means docs is no longer a good test of the item's CSS half. Either the item slims down upstream now the package exposes both, or docs accepts being a directmaterial-theme-builderconsumer and the item targets other sites only. Not decided here.The
globals.cssdiff in this PR is inert. The shadcn preset repaints 18 variables in the:root/.darkblocks; 17 of them are overridden by Move the colour engine tomaterial-theme-builderΒ #572'sshadcn.cssimport, whose selectors are doubled.--radiusis the only live one, and its value does not change. It is kept because it is what the CLI writes and hand-editing it would come back on every future run β Move the colour engine tomaterial-theme-builderΒ #572 adds a comment above the block saying so.material-theme-builder's shippeddist/tailwind.csscontains its own README's example custom colours (--color-myCustomColor1/2), so docs now carries 24 dead variables. Harmless, upstream, worth an issue there.shadcn addmatches its:root, .darkblock on the exact selector string; prettier had reformatted ours to:root,\n.dark, so re-installing appended a second identical copy. Removed by hand, twice over this branch. It will happen on every item update β worth an issue upstream too.The plan's CI check for missing tokens β extract the
--color-*names a block's utilities resolve to, diff against stock shadcn βͺ what itsregistryDependenciesemit β is still worth doing once there are several blocks. Nothing native does it.vitest.config.ts'sexclude: ['node_modules']is not a glob, so a barevitestwalks intonode_modulesand tries to run its packages' tests. Noticed in passing, left alone.π€ Generated with Claude Code
https://claude.ai/code/session_01EDvnsrory1aJsoNALn5aLB