Skip to content

feat(blocks): first ds-block-empty-state + contract-test target resolution - #9

Merged
codewizdave merged 4 commits into
mainfrom
feat/first-block-empty-state
Jul 30, 2026
Merged

feat(blocks): first ds-block-empty-state + contract-test target resolution#9
codewizdave merged 4 commits into
mainfrom
feat/first-block-empty-state

Conversation

@martyy-code

Copy link
Copy Markdown
Contributor

Summary

First entry in BLOCK_REGISTRY (the ds-block-* category). Single-file block that composes three existing ds-* items via registryDependencies. Also fixes a contract-test bug surfaced by the block.

What's new

Item Category Type Composes
ds-block-empty-state onboarding registry:block ds-empty, ds-button, ds-colored-badge

API: title?, description?, icon?, badge?: { label, color }, action?: { label, onClick }. All optional; sensible defaults render with no props.

Files

Showcase tree:

  • packages/registry/src/blocks/empty-state/index.tsx
  • packages/registry/src/blocks/empty-state/meta.ts
  • apps/web/lib/registry/index.tsxBLOCK_REGISTRY wired (was [])

Consumer tree:

  • registry/base-nova/ds-block-empty-state/ds-block-empty-state.tsx

Manifests:

  • registry.json — entry with type: registry:block + registryDependencies: [ds-empty, ds-button, ds-colored-badge]
  • docs/registry/audit-2026-07-29.json — drift audit entry
  • apps/web/public/r/ds-block-empty-state.json — generated
  • apps/web/public/r/registry.json — regenerated (catalog now 9 items)

Memory:

  • .claude/agent-memory/main/feedback_registry_deps_coupling.md — new
  • .claude/agent-memory/main/MEMORY.md — index updated

Contract-test fix (bundled)

The block imports @/components/ui/ds-empty, @/components/ui/ds-button, @/components/ui/ds-colored-badge. The contract test was doing a flat cpSync(SOURCE_DIR, components/ui) that preserved the directory layout registry/base-nova/ds-X/ds-X.tsx, which doesn't match what shadcn CLI does at install time (renames to components/ui/ds-X.tsx). The old layout happened to pass because no consumer file imported other ds-* items — the block is the first cross-import.

Fix: read each item's target from registry.json and copy to the resolved consumer-relative path (handles @ui/, @components/, @lib/, @hooks/, @/). Now the shim accurately simulates install behavior.

Validation

All green:

  • npm run build -w @workspace/registry
  • node apps/web/scripts/build-sources.mjs — 8 components, 1 block
  • node apps/web/scripts/build-registry.mjs — catalog + 9 items
  • node apps/web/scripts/check-registry-drift.mjs — no drift
  • cd apps/web && npx tsc --noEmit
  • next build — 7/7 pages
  • node apps/web/scripts/contract-test.mjs — consumer-shim tsc OK

🤖 Generated with Claude Code

…ution

Three concerns bundled in one commit because they are tightly coupled:

1. New block: ds-block-empty-state (category: onboarding)
   - Single-file block that composes ds-empty + ds-button + ds-colored-badge
     via registryDependencies (per the shadcn-registry-adoption plan's
     Blocks extension point convention).
   - First entry in BLOCK_REGISTRY. Showcase aggregator wires EmptyState
     and pulls source from SOURCES.blocks.
   - 4-surface pattern (showcase tree, consumer tree, registry.json,
     audit drift entry) same as components.
   - API: title, description, icon, badge, action - all optional, sensible
     defaults so the showcase preview renders with no props.

2. fix(contract-test): simulate shadcn CLI file rename via registry.json targets
   - The contract test previously did a flat cpSync(SOURCE_DIR, components/ui)
     which kept the registry/base-nova/ds-X/ds-X.tsx layout. Imports like
     @/components/ui/ds-empty would resolve to components/ui/ds-empty.tsx
     (the rename shadcn CLI applies at install time), not to
     components/ui/ds-empty/ds-empty.tsx.
   - The old layout happened to work because no consumer file imported
     other ds-* components. ds-block-empty-state breaks that - it
     composes three ds-* items.
   - Fix: read each item's target field from registry.json and copy to
     the resolved consumer-relative path. Handles @ui/, @components/,
     @lib/, @hooks/, @/ prefixes.
   - This makes the contract test accurately simulate what shadcn CLI
     does at install time, not just what the source tree looks like.

3. memory: project-level learnings
   - New file feedback_registry_deps_coupling.md captures the lesson
     from the ds-breadcrumb lucide-react incident (install list in
     contract-test.mjs must mirror registry.json dependencies).
   - MEMORY.md index updated.

Validation (all green):
- npm run build -w @workspace/registry
- node apps/web/scripts/build-sources.mjs (8 components, 1 block)
- node apps/web/scripts/build-registry.mjs (catalog + 9 items)
- node apps/web/scripts/check-registry-drift.mjs
- cd apps/web && npx tsc --noEmit
- next build (7/7 pages)
- node apps/web/scripts/contract-test.mjs

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ui-web Ready Ready Preview Jul 30, 2026 8:07am

…tion

When a block in packages/registry/src/blocks/ imports from
@workspace/registry/components/<id> (same package), TypeScript's
package.json exports map resolution fails without an explicit
rootDir: TS2209 'The project root is ambiguous, but is required to
resolve export map entry colored-badge'. The build tsconfig had it
(rootDir: ./src) but the typecheck tsconfig did not, so
npm run typecheck failed while npm run build worked.

Self-references via the workspace package only became relevant with
ds-block-empty-state, the first block that composes other ds-*
items. Components didn't hit this because they re-export from
@workspace/ui (cross-package), not within the registry itself.

Co-Authored-By: Claude <noreply@anthropic.com>
BlockEntry previously had no Demo field, so deriveBlockPreview rendered
<Block /> with no props. With all block props optional and a default
title of "Nothing here yet", the showcase preview tab showed placeholder
content instead of a realistic example.

Pattern was already in place for components (ComponentEntry has Demo,
deriveComponentPreview uses First.Demo). Extended the same to blocks:
- BlockEntry gains a required Demo field.
- deriveBlockPreview now renders <Demo /> instead of <Block />.
- The block author exports a BlockDemo that wraps the block with
  realistic props (badge, title, description, action).

This is parallel to the per-component Demo export convention; it makes
the preview tab useful without forcing the block itself to have a
no-args default that doubles as marketing.

Also regenerated sources.generated.ts (now contains the EmptyStateBlockDemo
export) and public/r/registry.json (catalog unchanged at 9 items, but
content hashes shift because the showcase source got longer).

Validation: drift clean, typecheck clean, next build green (7/7 pages).

Co-Authored-By: Claude <noreply@anthropic.com>
Previous commit (7f55aee) wired the Demo export pattern into the
aggregator and the /blocks index page, but missed two other places
that rendered blocks directly:

- apps/web/app/blocks/[category_id]/page.tsx rendered <item.Block />
  in the ItemCard preview slot. With the Demo convention, this should
  be <item.Demo /> so the card shows a realistic example instead of
  the empty-state default rendering.

- apps/web/app/blocks/[category_id]/[block_id]/page.tsx had a hardcoded
  "rendered block placeholder" string inside the Preview tab. Replaced
  it with <block.Demo /> so the tab actually renders the block.

Components pages already used <component.Demo /> correctly, so the
asymmetry was on the blocks side.

Co-Authored-By: Claude <noreply@anthropic.com>
@codewizdave
codewizdave merged commit 0b84f65 into main Jul 30, 2026
8 checks passed
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