Skip to content

Extract Keypoints into a registry block - #568

Closed
abernier wants to merge 2 commits into
md3-tier1from
keypoints-block
Closed

Extract Keypoints into a registry block#568
abernier wants to merge 2 commits into
md3-tier1from
keypoints-block

Conversation

@abernier

@abernier abernier commented Aug 16, 2026

Copy link
Copy Markdown
Member

PR 3 of pmndrs/design-system#1. Stacked on #567 — review that one first.

Keypoints becomes the first Tier 2 block of the pmndrs UI distribution:

npx shadcn@latest add pmndrs/docs/keypoints

It is also the first thing anywhere to resolve a dependency across registries. bg-surface-dim is an MD3 surface role shadcn has no equivalent for, so the item declares pmndrs/design-system/md3#v0.3.0 and one add pulls Tier 1 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, from src/components/mdx/Keypoints/ to registry/keypoints/, with registry.json at 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.

  • It reached into the MDX component mapimport { 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.
  • It imported cn through a path components.json does not alias. src/lib/cn.ts and src/lib/utils.ts were byte-equivalent, differing only in export style, and the utils alias — the one the CLI rewrites on install — pointed at the latter. First commit standardizes all 25 importers on it and deletes the duplicate.
  • It painted its border with border-outline-variant, which is border-border by another name once the MD3 remap is in place. It now uses the stock token, so a consumer who installed the block but skipped Tier 1 still gets a border. bg-surface-dim stays — that one is the deliberate reach, and the reason for the dependency.

Two smaller corrections while the API was becoming public: className now merges after the defaults rather than before, so a consumer can actually override them, and title is optional in the type as it already was in practice.

Verified

The plan calls the scratch-app install a required step rather than a nicety, because nothing in-repo can catch a missing token: registry validate never 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: a bare create-next-app outside any pmndrs repo, shadcn init --preset b5cR4Y50S, then shadcn add pmndrs/docs/keypoints#keypoints-block. It resolved the block, followed the dependency to pmndrs/design-system/md3#v0.3.0, installed material-theme-builder and the seed, and rendered a correctly-themed panel with nothing mounted — v0.3.0 bakes the palette into the CSS. Every utility the block uses emits a rule in the built CSS — checked individually, bg-surface-dim included, which resolves to var(--md-sys-color-surface-dim).

The pin was v0.2.0 at first, and that run only rendered because <Mtb> was mounted by hand: v0.2.0 is the shared layer without its bake, so installed on its own the panel has no background at all, and since the remap points the stock variables at MD3 roles, --background and --primary go with it. Silently, as ever. Re-verified at v0.3.0, which is where the pin belongs — it was sitting one commit up the stack, which would have released a colourless block if this merged alone.

Also: pnpm check-registry (new script, shadcn registry validate), tsc, eslint, prettier --check, vitest (95 passing), and a Storybook build with the story in its new home.

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. The renders themselves should be identical apart from the border token, which is 1:1.

Follow-ups, unchanged

The plan's CI check for missing tokens — extract the --color-* names a block's utilities resolve to, diff against stock shadcn ∪ what its registryDependencies emit — is still worth doing once there are several blocks. Nothing native does it. Not folded in here.

Separately, noticed in passing and left alone: src/stories/Page.tsx has two pre-existing react/no-unescaped-entities errors, and vitest.config.ts's exclude: ['node_modules'] is not a glob, so a bare vitest walks into node_modules and tries to run its packages' tests.


🤖 Generated with Claude Code

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.
@abernier

Copy link
Copy Markdown
Member Author

Collapsed into #570, which now targets main and carries all five commits of this stack. Nothing dropped — the whole narrative moved there.

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.

1 participant