diff --git a/.claude/agent-memory/main/MEMORY.md b/.claude/agent-memory/main/MEMORY.md index b09b505..8ea551e 100644 --- a/.claude/agent-memory/main/MEMORY.md +++ b/.claude/agent-memory/main/MEMORY.md @@ -2,5 +2,6 @@ - [User profile](user_profile.md) — French-speaking, shadcn/Tailwind v4 practitioner fighting agent-generated "slop UI" - [Concrete over theory](feedback_concrete_over_theory.md) — anchor design talk in current, verifiable tooling; label opinion as opinion +- [Registry deps coupling](feedback_registry_deps_coupling.md) — new registry item with new peer dep requires updating contract-test.mjs install list in lockstep - [Design learnings repo](project_design_learnings.md) — knowledge base + working monorepo for deessejs/ui registry at ui.deessejs.com (hosted on Vercel) - [Phase 4 validated](project_phase4_validated.md) — external install end-to-end confirmed 2026-07-29; Phase 6 (official shadcn index submission) gate is now lifted diff --git a/.claude/agent-memory/main/feedback_registry_deps_coupling.md b/.claude/agent-memory/main/feedback_registry_deps_coupling.md new file mode 100644 index 0000000..c7625f3 --- /dev/null +++ b/.claude/agent-memory/main/feedback_registry_deps_coupling.md @@ -0,0 +1,18 @@ +--- +name: feedback-registry-deps-coupling +description: Adding a new registry item with a new peer dep requires updating contract-test.mjs install list in lockstep — refactor target is to derive deps from registry.json +metadata: + type: feedback +--- + +When adding a new registry item whose `dependencies[]` introduces a peer dep the contract test hasn't installed, the CI `contract` job fails with TypeScript resolution errors (`Cannot find module 'X'`). Fix: add the new dep to the hardcoded install list in `apps/web/scripts/contract-test.mjs`. + +**Why:** during the 3→8 catalog expansion in this session, `ds-breadcrumb` introduced `lucide-react` (for `ChevronRightIcon` and `MoreHorizontalIcon`). The contract test passed locally because `lucide-react` happened to be present in the workspace `node_modules` from another package, but CI on a clean checkout failed. Cost: one fix-commit (`17d5f3d`) + one CI re-run. + +**How to apply:** +- Short term: when adding any registry item, audit its `dependencies[]` against the install list in `apps/web/scripts/contract-test.mjs` (lines ~84-99) and add any new ones before pushing. +- Long term: refactor `contract-test.mjs` to read `registry.json`, aggregate the union of `dependencies[]` across all items, and install that. Single source of truth = the catalog. Removes the coupling entirely. + +The drift script (`check-registry-drift.mjs`) has the same shape of coupling to `registry.json` but in the opposite direction (reads, doesn't install) — that one is fine as-is. + +Related: [[project-design-learnings]], [[project-phase4-validated]]. diff --git a/apps/web/app/blocks/[category_id]/[block_id]/page.tsx b/apps/web/app/blocks/[category_id]/[block_id]/page.tsx index 8255ed9..307c236 100644 --- a/apps/web/app/blocks/[category_id]/[block_id]/page.tsx +++ b/apps/web/app/blocks/[category_id]/[block_id]/page.tsx @@ -143,12 +143,7 @@ export default async function Page({ params }: { params: Params }) {
-
- - {block.name} - - rendered block placeholder -
+
diff --git a/apps/web/app/blocks/[category_id]/page.tsx b/apps/web/app/blocks/[category_id]/page.tsx index dc94114..9a03ec1 100644 --- a/apps/web/app/blocks/[category_id]/page.tsx +++ b/apps/web/app/blocks/[category_id]/page.tsx @@ -102,7 +102,7 @@ export default async function Page({ params }: { params: Params }) { href={`/blocks/${category_id}/${item.id}`} name={item.name} description={item.description} - preview={} + preview={} className={spansTwo ? "sm:col-span-2" : undefined} previewClassName={spansTwo ? "sm:h-60" : undefined} /> diff --git a/apps/web/lib/registry/index.tsx b/apps/web/lib/registry/index.tsx index 2f65f9a..8eab9fb 100644 --- a/apps/web/lib/registry/index.tsx +++ b/apps/web/lib/registry/index.tsx @@ -38,6 +38,11 @@ import { TextareaDemo, } from "@workspace/registry/components/textarea" import { meta as textareaMeta } from "@workspace/registry/components/textarea/meta" +import { + EmptyState, + EmptyStateBlockDemo, +} from "@workspace/registry/blocks/empty-state" +import { meta as emptyStateMeta } from "@workspace/registry/blocks/empty-state/meta" import { SOURCES } from "./sources" import type { ComponentMeta, BlockMeta } from "./types" @@ -53,6 +58,8 @@ type ComponentEntry = ComponentMeta & { type BlockEntry = BlockMeta & { // eslint-disable-next-line @typescript-eslint/no-explicit-any Block: React.ComponentType + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Demo: React.ComponentType source: string } @@ -107,7 +114,14 @@ const COMPONENT_REGISTRY: ComponentEntry[] = [ }, ] -const BLOCK_REGISTRY: BlockEntry[] = [] +const BLOCK_REGISTRY: BlockEntry[] = [ + { + ...emptyStateMeta, + Block: EmptyState, + Demo: EmptyStateBlockDemo, + source: SOURCES.blocks["empty-state"], + }, +] const CATEGORY_LABELS: Record = { buttons: "Buttons", @@ -196,8 +210,8 @@ function deriveComponentPreview(items: ComponentEntry[]): React.ComponentType { function deriveBlockPreview(items: BlockEntry[]): React.ComponentType { const First = items[0]! - const Block = First.Block - return () => + const Demo = First.Demo + return () => } export function getEnrichedComponentCategories(): EnrichedComponentCategory[] { diff --git a/apps/web/lib/registry/sources.generated.ts b/apps/web/lib/registry/sources.generated.ts index 87f29f8..4fcd3cd 100644 --- a/apps/web/lib/registry/sources.generated.ts +++ b/apps/web/lib/registry/sources.generated.ts @@ -4,14 +4,16 @@ export const SOURCES = { components: { - "breadcrumb": "\"use client\"\n\nimport {\n Breadcrumb as ShadcnBreadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbPage,\n BreadcrumbSeparator,\n BreadcrumbEllipsis,\n} from \"@workspace/ui/components/breadcrumb\"\n\nexport {\n ShadcnBreadcrumb as Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbPage,\n BreadcrumbSeparator,\n BreadcrumbEllipsis,\n}\n\nexport function BreadcrumbDemo() {\n return (\n \n \n \n Home\n \n \n \n Components\n \n \n \n Breadcrumb\n \n \n \n )\n}\n", + "breadcrumb": "\"use client\"\r\n\r\nimport {\r\n Breadcrumb as ShadcnBreadcrumb,\r\n BreadcrumbList,\r\n BreadcrumbItem,\r\n BreadcrumbLink,\r\n BreadcrumbPage,\r\n BreadcrumbSeparator,\r\n BreadcrumbEllipsis,\r\n} from \"@workspace/ui/components/breadcrumb\"\r\n\r\nexport {\r\n ShadcnBreadcrumb as Breadcrumb,\r\n BreadcrumbList,\r\n BreadcrumbItem,\r\n BreadcrumbLink,\r\n BreadcrumbPage,\r\n BreadcrumbSeparator,\r\n BreadcrumbEllipsis,\r\n}\r\n\r\nexport function BreadcrumbDemo() {\r\n return (\r\n \r\n \r\n \r\n Home\r\n \r\n \r\n \r\n Components\r\n \r\n \r\n \r\n Breadcrumb\r\n \r\n \r\n \r\n )\r\n}\r\n", "button": "\"use client\"\n\nimport { Button as ShadcnButton } from \"@workspace/ui/components/button\"\n\nexport type ButtonProps = React.ComponentProps\n\nexport { ShadcnButton as Button }\n\nexport function ButtonDemo() {\n return (\n
\n Default\n Secondary\n Outline\n Ghost\n Destructive\n
\n )\n}", "colored-badge": "\"use client\"\r\n\r\nimport { Badge } from \"@workspace/ui/components/badge\"\r\nimport { cn } from \"@workspace/ui/lib/utils\"\r\n\r\nexport type ColoredBadgeColor =\r\n | \"blue\"\r\n | \"green\"\r\n | \"red\"\r\n | \"yellow\"\r\n | \"orange\"\r\n | \"purple\"\r\n | \"pink\"\r\n | \"gray\"\r\n\r\nconst COLOR_CLASSES: Record = {\r\n blue: \"bg-blue-600/10 text-blue-500 border-blue-500/20\",\r\n green: \"bg-green-500/10 text-green-500 border-green-500/20\",\r\n red: \"bg-red-500/10 text-red-500 border-red-500/20\",\r\n yellow: \"bg-yellow-500/10 text-yellow-500 border-yellow-500/20\",\r\n orange: \"bg-orange-500/10 text-orange-500 border-orange-500/20\",\r\n purple: \"bg-purple-500/10 text-purple-500 border-purple-500/20\",\r\n pink: \"bg-pink-500/10 text-pink-500 border-pink-500/20\",\r\n gray: \"bg-gray-500/10 text-gray-500 border-gray-500/20\",\r\n}\r\n\r\nexport interface ColoredBadgeProps {\r\n color: ColoredBadgeColor\r\n children: React.ReactNode\r\n}\r\n\r\nexport function ColoredBadge({ color, children }: ColoredBadgeProps) {\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport function ColoredBadgeDemo() {\r\n return (\r\n
\r\n Blue\r\n Green\r\n Red\r\n Yellow\r\n Orange\r\n Purple\r\n Pink\r\n Gray\r\n
\r\n )\r\n}", - "empty": "\"use client\"\n\nimport {\n Empty as ShadcnEmpty,\n EmptyHeader,\n EmptyTitle,\n EmptyDescription,\n EmptyContent,\n EmptyMedia,\n} from \"@workspace/ui/components/empty\"\n\nexport {\n ShadcnEmpty as Empty,\n EmptyHeader,\n EmptyTitle,\n EmptyDescription,\n EmptyContent,\n EmptyMedia,\n}\n\nexport function EmptyDemo() {\n return (\n \n \n ⊙\n No projects yet\n \n Create your first project to start tracking work.\n \n \n \n \n Create project\n \n \n \n )\n}\n", + "empty": "\"use client\"\r\n\r\nimport {\r\n Empty as ShadcnEmpty,\r\n EmptyHeader,\r\n EmptyTitle,\r\n EmptyDescription,\r\n EmptyContent,\r\n EmptyMedia,\r\n} from \"@workspace/ui/components/empty\"\r\n\r\nexport {\r\n ShadcnEmpty as Empty,\r\n EmptyHeader,\r\n EmptyTitle,\r\n EmptyDescription,\r\n EmptyContent,\r\n EmptyMedia,\r\n}\r\n\r\nexport function EmptyDemo() {\r\n return (\r\n \r\n \r\n ⊙\r\n No projects yet\r\n \r\n Create your first project to start tracking work.\r\n \r\n \r\n \r\n \r\n Create project\r\n \r\n \r\n \r\n )\r\n}\r\n", "icon-button": "\"use client\"\n\nimport { Button as ShadcnButton } from \"@workspace/ui/components/button\"\nimport { cn } from \"@workspace/ui/lib/utils\"\n\nexport interface IconButtonProps\n extends Omit, \"children\" | \"size\"> {\n \"aria-label\": string\n children: React.ReactNode\n size?: \"sm\" | \"md\" | \"lg\"\n}\n\nconst SIZE_CLASSES: Record, string> = {\n sm: \"size-8\",\n md: \"size-10\",\n lg: \"size-12\",\n}\n\nexport function IconButton({\n className,\n size = \"md\",\n type = \"button\",\n ...props\n}: IconButtonProps) {\n return (\n \n )\n}\n\nexport function IconButtonDemo() {\n return (\n
\n \n +\n \n \n ✎\n \n \n ×\n \n
\n )\n}", - "input": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@workspace/ui/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLAttributes {}\n\nexport const Input = React.forwardRef(\n ({ className, type, ...props }, ref) => (\n \n )\n)\nInput.displayName = \"Input\"\n\nexport function InputDemo() {\n return (\n
\n \n \n \n \n \n
\n )\n}\n", - "tabs": "\"use client\"\n\nimport {\n Tabs as ShadcnTabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n} from \"@workspace/ui/components/tabs\"\n\nexport {\n ShadcnTabs as Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n}\n\nexport function TabsDemo() {\n return (\n \n \n Overview\n Analytics\n Settings\n \n Overview panel content.\n Analytics panel content.\n Settings panel content.\n \n )\n}\n", - "textarea": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@workspace/ui/lib/utils\"\n\nexport interface TextareaProps\n extends React.TextareaHTMLAttributes {}\n\nexport const Textarea = React.forwardRef(\n ({ className, ...props }, ref) => (\n \n )\n)\nTextarea.displayName = \"Textarea\"\n\nexport function TextareaDemo() {\n return (\n
\n