@withmata/blueprints 0.3.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/audit/SKILL.md +4 -4
- package/.claude/skills/blueprint-catalog/SKILL.md +17 -7
- package/.claude/skills/copywrite/SKILL.md +187 -0
- package/.claude/skills/copywrite-landing/SKILL.md +489 -0
- package/.claude/skills/design-system/SKILL.md +970 -0
- package/.claude/skills/new-project/SKILL.md +168 -112
- package/.claude/skills/scaffold-auth/SKILL.md +9 -9
- package/.claude/skills/scaffold-db/SKILL.md +14 -14
- package/.claude/skills/scaffold-env/SKILL.md +4 -4
- package/.claude/skills/scaffold-foundation/SKILL.md +15 -15
- package/.claude/skills/scaffold-tailwind/SKILL.md +17 -3
- package/.claude/skills/scaffold-ui/SKILL.md +155 -36
- package/ENGINEERING.md +2 -2
- package/blueprints/discovery/design-system/BLUEPRINT.md +1479 -0
- package/blueprints/discovery/marketing-copywriting/BLUEPRINT.md +664 -0
- package/blueprints/features/auth-better-auth/BLUEPRINT.md +20 -22
- package/blueprints/features/db-drizzle-postgres/BLUEPRINT.md +12 -12
- package/blueprints/features/db-drizzle-postgres/files/db/src/example-entity.ts +1 -1
- package/blueprints/features/db-drizzle-postgres/files/db/src/scripts/seed.ts +1 -1
- package/blueprints/features/env-t3/BLUEPRINT.md +1 -1
- package/blueprints/features/tailwind-v4/BLUEPRINT.md +9 -2
- package/blueprints/features/tailwind-v4/files/tailwind-config/shared-styles.css +80 -1
- package/blueprints/features/ui-shared-components/BLUEPRINT.md +411 -78
- package/blueprints/features/ui-shared-components/files/ui/components/ui/alert-dialog.tsx +192 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/avatar.tsx +71 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/badge.tsx +52 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/breadcrumb.tsx +122 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/button.tsx +56 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/card-select.tsx +72 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/card.tsx +100 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/collapsible.tsx +34 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/combobox.tsx +301 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/dropdown-menu.tsx +264 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/empty-state.tsx +43 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/entity-select.tsx +110 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/field.tsx +237 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/form-field.tsx +217 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/input-group.tsx +161 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/input.tsx +20 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/label.tsx +20 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/org-switcher.tsx +114 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/page-header.tsx +45 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/pagination.tsx +52 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/pill-select.tsx +151 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/popover.tsx +41 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/search-input.tsx +49 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/select.tsx +205 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/selected-entity-card.tsx +47 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/separator.tsx +25 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/sidebar.tsx +389 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/status-filter.tsx +43 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/tag-input.tsx +131 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/textarea.tsx +18 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/user-menu.tsx +149 -0
- package/blueprints/features/ui-shared-components/files/ui/components.json +11 -8
- package/blueprints/features/ui-shared-components/files/ui/package.json +20 -11
- package/blueprints/foundation/monorepo-turbo/BLUEPRINT.md +19 -20
- package/blueprints/foundation/monorepo-turbo/files/root/package.json +1 -1
- package/dist/index.js +27 -10
- package/package.json +1 -1
- package/blueprints/features/tailwind-v4/files/tailwind-config/package.json +0 -20
- package/blueprints/foundation/monorepo-turbo/files/root/pnpm-workspace.yaml +0 -5
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "#components/ui/button";
|
|
4
|
+
import { cn } from "#utils/cn";
|
|
5
|
+
import { PlusIcon, X } from "@phosphor-icons/react";
|
|
6
|
+
import { type KeyboardEvent, useRef, useState } from "react";
|
|
7
|
+
|
|
8
|
+
export interface TagInputProps {
|
|
9
|
+
value: string[];
|
|
10
|
+
onChange: (value: string[]) => void;
|
|
11
|
+
maxItems: number;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
hideCounter?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function TagInput({
|
|
19
|
+
value,
|
|
20
|
+
onChange,
|
|
21
|
+
maxItems,
|
|
22
|
+
placeholder = "Type and press Enter...",
|
|
23
|
+
disabled = false,
|
|
24
|
+
className,
|
|
25
|
+
hideCounter = false,
|
|
26
|
+
}: TagInputProps) {
|
|
27
|
+
const [inputValue, setInputValue] = useState("");
|
|
28
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
29
|
+
|
|
30
|
+
const isAtMax = value.length >= maxItems;
|
|
31
|
+
|
|
32
|
+
const addTag = () => {
|
|
33
|
+
const trimmed = inputValue.trim();
|
|
34
|
+
if (!trimmed || value.length >= maxItems) return;
|
|
35
|
+
|
|
36
|
+
// Check for duplicates (case-insensitive)
|
|
37
|
+
if (value.some((v) => v.toLowerCase() === trimmed.toLowerCase())) {
|
|
38
|
+
setInputValue("");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
onChange([...value, trimmed]);
|
|
43
|
+
setInputValue("");
|
|
44
|
+
inputRef.current?.focus();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
48
|
+
if (e.key === "Enter" && inputValue.trim()) {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
addTag();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Handle removing a tag
|
|
55
|
+
const handleRemove = (indexToRemove: number) => {
|
|
56
|
+
onChange(value.filter((_, index) => index !== indexToRemove));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className={cn("space-y-2", className)}>
|
|
61
|
+
{/* Input field */}
|
|
62
|
+
{!isAtMax && (
|
|
63
|
+
<div
|
|
64
|
+
className={cn(
|
|
65
|
+
"bg-input/30 border-input focus-within:border-primary/90 flex min-h-9 items-center gap-1.5 rounded-4xl border bg-clip-padding px-2.5 py-1.5 text-sm transition-colors",
|
|
66
|
+
disabled && "opacity-60 cursor-not-allowed",
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
<input
|
|
70
|
+
ref={inputRef}
|
|
71
|
+
type="text"
|
|
72
|
+
value={inputValue}
|
|
73
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
74
|
+
onKeyDown={handleKeyDown}
|
|
75
|
+
placeholder={value.length === 0 ? placeholder : "Add another..."}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
className="min-w-16 flex-1 outline-none bg-transparent"
|
|
78
|
+
/>
|
|
79
|
+
{inputValue.trim() && (
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
onClick={addTag}
|
|
83
|
+
disabled={disabled}
|
|
84
|
+
className="flex items-center gap-0.5 shrink-0 rounded-full bg-primary/10 text-primary px-2 py-0.5 text-xs font-medium hover:bg-primary/20 transition-colors"
|
|
85
|
+
>
|
|
86
|
+
<PlusIcon className="size-3" weight="bold" />
|
|
87
|
+
Add
|
|
88
|
+
</button>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{/* Tags */}
|
|
94
|
+
{value.length > 0 && (
|
|
95
|
+
<div className="flex flex-wrap gap-1.5">
|
|
96
|
+
{value.map((tag, index) => (
|
|
97
|
+
<div
|
|
98
|
+
key={`${tag}-${index}`}
|
|
99
|
+
className="bg-muted-foreground/10 text-foreground flex h-7 w-fit items-center justify-center gap-1 rounded-4xl px-2.5 pr-0.5 text-sm font-medium whitespace-nowrap"
|
|
100
|
+
>
|
|
101
|
+
{tag}
|
|
102
|
+
<Button
|
|
103
|
+
type="button"
|
|
104
|
+
variant="ghost"
|
|
105
|
+
size="icon-xs"
|
|
106
|
+
aria-label={`Remove ${tag}`}
|
|
107
|
+
onClick={() => handleRemove(index)}
|
|
108
|
+
disabled={disabled}
|
|
109
|
+
className="-ml-1 opacity-50 hover:opacity-100"
|
|
110
|
+
>
|
|
111
|
+
<X className="pointer-events-none size-3" />
|
|
112
|
+
</Button>
|
|
113
|
+
</div>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{/* Counter */}
|
|
119
|
+
{!hideCounter && (
|
|
120
|
+
<p
|
|
121
|
+
className={cn(
|
|
122
|
+
"text-xs text-muted-foreground",
|
|
123
|
+
isAtMax && "text-destructive font-medium",
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
{value.length}/{maxItems} items{isAtMax && " (max)"}
|
|
127
|
+
</p>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "#utils/cn";
|
|
4
|
+
|
|
5
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
6
|
+
return (
|
|
7
|
+
<textarea
|
|
8
|
+
data-slot="textarea"
|
|
9
|
+
className={cn(
|
|
10
|
+
"border-input bg-input/30 focus-visible:border-primary/90 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 resize-none rounded-xl border px-3 py-2 text-base transition-colors md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Textarea };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuGroup,
|
|
7
|
+
DropdownMenuItem,
|
|
8
|
+
DropdownMenuSeparator,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from "#components/ui/dropdown-menu";
|
|
11
|
+
import {
|
|
12
|
+
SidebarMenu,
|
|
13
|
+
SidebarMenuButton,
|
|
14
|
+
SidebarMenuItem,
|
|
15
|
+
useSidebar,
|
|
16
|
+
} from "#components/ui/sidebar";
|
|
17
|
+
import { Avatar } from "#components/ui/avatar";
|
|
18
|
+
import { cn } from "#utils/cn";
|
|
19
|
+
import { CaretUpDownIcon } from "@phosphor-icons/react/CaretUpDown";
|
|
20
|
+
import { GearIcon } from "@phosphor-icons/react/Gear";
|
|
21
|
+
import { SignOutIcon } from "@phosphor-icons/react/SignOut";
|
|
22
|
+
import { UserIcon } from "@phosphor-icons/react/User";
|
|
23
|
+
import type * as React from "react";
|
|
24
|
+
|
|
25
|
+
export interface UserMenuUser {
|
|
26
|
+
name: string;
|
|
27
|
+
email: string;
|
|
28
|
+
image?: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface UserMenuItem {
|
|
32
|
+
label: string;
|
|
33
|
+
icon: React.ReactNode;
|
|
34
|
+
onClick: () => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UserMenuProps {
|
|
38
|
+
user: UserMenuUser | null;
|
|
39
|
+
isLoading?: boolean;
|
|
40
|
+
items?: UserMenuItem[];
|
|
41
|
+
onSignOut?: () => void;
|
|
42
|
+
onSettingsClick?: () => void;
|
|
43
|
+
onProfileClick?: () => void;
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function UserMenu({
|
|
48
|
+
user,
|
|
49
|
+
isLoading = false,
|
|
50
|
+
items,
|
|
51
|
+
onSignOut,
|
|
52
|
+
onSettingsClick,
|
|
53
|
+
onProfileClick,
|
|
54
|
+
className,
|
|
55
|
+
}: UserMenuProps) {
|
|
56
|
+
const { state } = useSidebar();
|
|
57
|
+
const isCollapsed = state === "collapsed";
|
|
58
|
+
|
|
59
|
+
if (isLoading) {
|
|
60
|
+
return (
|
|
61
|
+
<SidebarMenu>
|
|
62
|
+
<SidebarMenuItem>
|
|
63
|
+
<SidebarMenuButton size="lg" className="animate-pulse">
|
|
64
|
+
<div className="size-8 rounded-lg bg-sidebar-accent" />
|
|
65
|
+
{!isCollapsed && (
|
|
66
|
+
<div className="flex-1 space-y-1">
|
|
67
|
+
<div className="h-3 w-20 rounded bg-sidebar-accent" />
|
|
68
|
+
<div className="h-2 w-24 rounded bg-sidebar-accent" />
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
</SidebarMenuButton>
|
|
72
|
+
</SidebarMenuItem>
|
|
73
|
+
</SidebarMenu>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!user) return null;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<SidebarMenu className={cn(className)}>
|
|
81
|
+
<SidebarMenuItem>
|
|
82
|
+
<DropdownMenu>
|
|
83
|
+
<DropdownMenuTrigger
|
|
84
|
+
render={
|
|
85
|
+
<SidebarMenuButton
|
|
86
|
+
size="lg"
|
|
87
|
+
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
88
|
+
>
|
|
89
|
+
<Avatar
|
|
90
|
+
name={user.name}
|
|
91
|
+
image={user.image}
|
|
92
|
+
size="default"
|
|
93
|
+
className="bg-sidebar-primary text-sidebar-primary-foreground"
|
|
94
|
+
/>
|
|
95
|
+
{!isCollapsed && (
|
|
96
|
+
<>
|
|
97
|
+
<div className="grid flex-1 text-left text-sm leading-tight overflow-hidden">
|
|
98
|
+
<span className="truncate font-semibold">
|
|
99
|
+
{user.name}
|
|
100
|
+
</span>
|
|
101
|
+
<span className="truncate text-xs text-sidebar-foreground/70">
|
|
102
|
+
{user.email}
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
105
|
+
<CaretUpDownIcon className="ml-auto size-4 shrink-0" />
|
|
106
|
+
</>
|
|
107
|
+
)}
|
|
108
|
+
</SidebarMenuButton>
|
|
109
|
+
}
|
|
110
|
+
/>
|
|
111
|
+
<DropdownMenuContent
|
|
112
|
+
side={isCollapsed ? "right" : "top"}
|
|
113
|
+
align="start"
|
|
114
|
+
>
|
|
115
|
+
<DropdownMenuGroup>
|
|
116
|
+
{onProfileClick && (
|
|
117
|
+
<DropdownMenuItem onClick={onProfileClick}>
|
|
118
|
+
<UserIcon className="size-4" />
|
|
119
|
+
<span>Profile</span>
|
|
120
|
+
</DropdownMenuItem>
|
|
121
|
+
)}
|
|
122
|
+
{onSettingsClick && (
|
|
123
|
+
<DropdownMenuItem onClick={onSettingsClick}>
|
|
124
|
+
<GearIcon className="size-4" />
|
|
125
|
+
<span>Settings</span>
|
|
126
|
+
</DropdownMenuItem>
|
|
127
|
+
)}
|
|
128
|
+
{items?.map((item) => (
|
|
129
|
+
<DropdownMenuItem key={item.label} onClick={item.onClick}>
|
|
130
|
+
{item.icon}
|
|
131
|
+
<span>{item.label}</span>
|
|
132
|
+
</DropdownMenuItem>
|
|
133
|
+
))}
|
|
134
|
+
</DropdownMenuGroup>
|
|
135
|
+
{onSignOut && (
|
|
136
|
+
<>
|
|
137
|
+
<DropdownMenuSeparator />
|
|
138
|
+
<DropdownMenuItem onClick={onSignOut}>
|
|
139
|
+
<SignOutIcon className="size-4" />
|
|
140
|
+
<span>Sign out</span>
|
|
141
|
+
</DropdownMenuItem>
|
|
142
|
+
</>
|
|
143
|
+
)}
|
|
144
|
+
</DropdownMenuContent>
|
|
145
|
+
</DropdownMenu>
|
|
146
|
+
</SidebarMenuItem>
|
|
147
|
+
</SidebarMenu>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
-
"style": "
|
|
3
|
+
"style": "base-maia",
|
|
4
4
|
"rsc": true,
|
|
5
5
|
"tsx": true,
|
|
6
6
|
"tailwind": {
|
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
"cssVariables": true,
|
|
11
11
|
"prefix": ""
|
|
12
12
|
},
|
|
13
|
-
"iconLibrary": "
|
|
13
|
+
"iconLibrary": "phosphor",
|
|
14
14
|
"aliases": {
|
|
15
|
-
"components": "
|
|
16
|
-
"utils": "
|
|
17
|
-
"ui": "
|
|
18
|
-
"lib": "
|
|
19
|
-
"hooks": "
|
|
20
|
-
}
|
|
15
|
+
"components": "components",
|
|
16
|
+
"utils": "utils",
|
|
17
|
+
"ui": "components/ui",
|
|
18
|
+
"lib": "utils",
|
|
19
|
+
"hooks": "hooks"
|
|
20
|
+
},
|
|
21
|
+
"menuColor": "default",
|
|
22
|
+
"menuAccent": "subtle",
|
|
23
|
+
"registries": {}
|
|
21
24
|
}
|
|
@@ -3,33 +3,42 @@
|
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
-
"ui:add": "
|
|
6
|
+
"ui:add": "bunx shadcn@latest add"
|
|
7
7
|
},
|
|
8
8
|
"imports": {
|
|
9
9
|
"#components/*": "./components/*.tsx",
|
|
10
10
|
"#components/ui/*": "./components/ui/*.tsx",
|
|
11
|
+
"#components/base/*": "./components/base/*.tsx",
|
|
12
|
+
"#components/structure/*": "./components/structure/*.tsx",
|
|
11
13
|
"#utils/*": "./utils/*.ts",
|
|
12
|
-
"#hooks/*": "./hooks/*.ts",
|
|
13
14
|
"#types/*": "./types/*.ts"
|
|
14
15
|
},
|
|
15
16
|
"exports": {
|
|
16
17
|
"./components/*": "./components/*.tsx",
|
|
17
18
|
"./components/ui/*": "./components/ui/*.tsx",
|
|
19
|
+
"./components/base/*": "./components/base/*.tsx",
|
|
20
|
+
"./components/structure/*": "./components/structure/*.tsx",
|
|
18
21
|
"./utils/*": "./utils/*.ts",
|
|
22
|
+
"./types/*": "./types/*.ts",
|
|
19
23
|
"./styles": "./styles/globals.css",
|
|
20
24
|
"./icons/*": "./icons/*.tsx",
|
|
21
|
-
"./icons/logos/*": "./icons/logos/*.tsx"
|
|
25
|
+
"./icons/logos/*": "./icons/logos/*.tsx",
|
|
26
|
+
"./icons/company-assets/*": "./icons/company-assets/*.tsx"
|
|
22
27
|
},
|
|
23
28
|
"dependencies": {
|
|
24
29
|
"{{SCOPE}}/tailwind-config": "workspace:*",
|
|
25
|
-
"@
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"react": "^
|
|
29
|
-
"react-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
30
|
+
"@base-ui/react": "^1.0.0",
|
|
31
|
+
"@phosphor-icons/react": "^2.1.10",
|
|
32
|
+
"@radix-ui/react-collapsible": "^1.1.12",
|
|
33
|
+
"@radix-ui/react-popover": "^1.1.15",
|
|
34
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
35
|
+
"class-variance-authority": "^0.7.1",
|
|
36
|
+
"clsx": "^2.1.1",
|
|
37
|
+
"react": "^19.1.0",
|
|
38
|
+
"react-dom": "^19.1.0",
|
|
39
|
+
"shadcn": "^3.6.2",
|
|
40
|
+
"tailwind-merge": "^3.4.0",
|
|
41
|
+
"tw-animate-css": "^1.4.0"
|
|
33
42
|
},
|
|
34
43
|
"devDependencies": {
|
|
35
44
|
"{{SCOPE}}/typescript-config": "workspace:*",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Monorepo Foundation Blueprint — Turborepo +
|
|
1
|
+
# Monorepo Foundation Blueprint — Turborepo + bun
|
|
2
2
|
|
|
3
3
|
## Tier
|
|
4
4
|
|
|
@@ -49,8 +49,7 @@ This blueprint generates the project skeleton:
|
|
|
49
49
|
├── packages/ # Empty — feature blueprints add packages here
|
|
50
50
|
├── .gitignore
|
|
51
51
|
├── biome.json # Linting + formatting
|
|
52
|
-
├── package.json # Root workspace scripts
|
|
53
|
-
├── pnpm-workspace.yaml # Workspace patterns
|
|
52
|
+
├── package.json # Root workspace scripts + workspaces field
|
|
54
53
|
└── turbo.json # Task orchestration
|
|
55
54
|
```
|
|
56
55
|
|
|
@@ -64,9 +63,9 @@ This blueprint generates the project skeleton:
|
|
|
64
63
|
|
|
65
64
|
## Architecture
|
|
66
65
|
|
|
67
|
-
### Monorepo Tool: Turborepo +
|
|
66
|
+
### Monorepo Tool: Turborepo + bun
|
|
68
67
|
|
|
69
|
-
- **
|
|
68
|
+
- **bun workspaces** — Fast, all-in-one JavaScript runtime with built-in package management, workspace support, and near-instant installs. Workspaces are declared in the root `package.json` `workspaces` field.
|
|
70
69
|
- **Turborepo** — Task orchestration with dependency-aware caching. `dev` runs all workspaces in parallel. `build` respects dependency order via `^build`.
|
|
71
70
|
- **Workspace protocol** — Internal deps use `workspace:*` for automatic linking. No manual version tracking between packages.
|
|
72
71
|
|
|
@@ -195,10 +194,10 @@ apps/web/
|
|
|
195
194
|
| Choice | Default | Alternatives |
|
|
196
195
|
|--------|---------|-------------|
|
|
197
196
|
| npm scope | Derived from product name | Any valid npm scope |
|
|
198
|
-
| Package manager |
|
|
197
|
+
| Package manager | bun | pnpm (documented alternative) |
|
|
199
198
|
| Node.js version | `>=22` | Any LTS version |
|
|
200
199
|
|
|
201
|
-
**Note on
|
|
200
|
+
**Note on pnpm:** If using pnpm instead of bun, create a `pnpm-workspace.yaml` with workspace patterns, add a `"packageManager": "pnpm@9.x"` field to the root `package.json`, and remove the `workspaces` field (pnpm reads its own config file). Replace `bun install` with `pnpm install`, `bunx` with `pnpm dlx`, and `bun add -d` with `pnpm add -D`. The workspace protocol (`workspace:*`) works the same way.
|
|
202
201
|
|
|
203
202
|
---
|
|
204
203
|
|
|
@@ -206,7 +205,7 @@ apps/web/
|
|
|
206
205
|
|
|
207
206
|
These are locked-down decisions. The blueprint doesn't offer alternatives for these:
|
|
208
207
|
|
|
209
|
-
- **
|
|
208
|
+
- **bun** as package manager (pnpm documented as alternative, but npm and yarn are not supported)
|
|
210
209
|
- **Turborepo** for monorepo task orchestration (not Nx, Lerna, or Rush)
|
|
211
210
|
- **Biome** for linting + formatting (not ESLint + Prettier)
|
|
212
211
|
- **Strict TypeScript** — `strict: true` and `noUncheckedIndexedAccess: true` in every package
|
|
@@ -277,9 +276,8 @@ If no discovery was run, the foundation asks these questions directly.
|
|
|
277
276
|
|
|
278
277
|
| Blueprint File | Target Location | Description |
|
|
279
278
|
|----------------|-----------------|-------------|
|
|
280
|
-
| `files/root/package.json` | `package.json` | Root monorepo package.json with workspace scripts |
|
|
279
|
+
| `files/root/package.json` | `package.json` | Root monorepo package.json with workspace scripts and workspaces field |
|
|
281
280
|
| `files/root/turbo.json` | `turbo.json` | Turborepo task configuration |
|
|
282
|
-
| `files/root/pnpm-workspace.yaml` | `pnpm-workspace.yaml` | Workspace patterns |
|
|
283
281
|
| `files/root/biome.json` | `biome.json` | Biome linter + formatter config |
|
|
284
282
|
| `files/root/gitignore` | `.gitignore` | Git ignore patterns |
|
|
285
283
|
| `files/config/typescript-config/package.json` | `config/typescript-config/package.json` | TypeScript config package |
|
|
@@ -332,20 +330,20 @@ The lefthook config is minimal by design. Feature blueprints may add their own c
|
|
|
332
330
|
|
|
333
331
|
| When you... | Then run... | Why |
|
|
334
332
|
|---|---|---|
|
|
335
|
-
| Add a new workspace package to `apps/`, `apis/`, `packages/`, or `config/` | Verify `
|
|
336
|
-
| Change a shared config package (`config/typescript-config/` or `config/tailwind-config/`) | Run `
|
|
337
|
-
| Add a workspace dependency (e.g., `"@scope/ui": "workspace:*"`) | Run `
|
|
338
|
-
| Modify `turbo.json` task definitions | Verify `
|
|
339
|
-
| Change the TypeScript `base.json` config | Run typecheck across all workspaces: `
|
|
333
|
+
| Add a new workspace package to `apps/`, `apis/`, `packages/`, or `config/` | Verify the `workspaces` field in root `package.json` covers it, then run `bun install` | New workspaces need to be discovered by bun |
|
|
334
|
+
| Change a shared config package (`config/typescript-config/` or `config/tailwind-config/`) | Run `bun dev` and verify all consuming packages still build | Shared config changes cascade to all consumers |
|
|
335
|
+
| Add a workspace dependency (e.g., `"@scope/ui": "workspace:*"`) | Run `bun install` then verify imports resolve | Workspace links need bun to wire them up |
|
|
336
|
+
| Modify `turbo.json` task definitions | Verify `bun turbo run <task>` executes in the expected order | Task dependency changes affect build orchestration |
|
|
337
|
+
| Change the TypeScript `base.json` config | Run typecheck across all workspaces: `bun turbo typecheck` | Base config changes affect every package |
|
|
340
338
|
|
|
341
339
|
### Condensed Rules for CLAUDE.md
|
|
342
340
|
```markdown
|
|
343
341
|
### Monorepo maintenance
|
|
344
|
-
- After every code change: format with Biome (`
|
|
345
|
-
- After adding new workspace packages: verify `
|
|
342
|
+
- After every code change: format with Biome (`bun biome check --write .`) before considering work complete
|
|
343
|
+
- After adding new workspace packages: verify `workspaces` field in root `package.json` includes the pattern, run `bun install`
|
|
346
344
|
- After modifying shared configs (`config/typescript-config/`, `config/tailwind-config/`): verify all consumers still build
|
|
347
|
-
- After changing `turbo.json`: verify task ordering with `
|
|
348
|
-
- After adding workspace dependencies: run `
|
|
345
|
+
- After changing `turbo.json`: verify task ordering with `bun turbo run <task> --dry`
|
|
346
|
+
- After adding workspace dependencies: run `bun install` to create workspace links
|
|
349
347
|
- Keep `turbo.json` tasks with `cache: false` for any task with side effects (migrations, code generation)
|
|
350
348
|
```
|
|
351
349
|
|
|
@@ -358,7 +356,7 @@ After completion, appends to `.claude/project-context.md`:
|
|
|
358
356
|
```yaml
|
|
359
357
|
## Foundation
|
|
360
358
|
monorepo_tool: turborepo
|
|
361
|
-
package_manager:
|
|
359
|
+
package_manager: bun
|
|
362
360
|
npm_scope: "{{SCOPE}}"
|
|
363
361
|
node_version: ">=22"
|
|
364
362
|
workspaces:
|
|
@@ -376,3 +374,4 @@ installed_at: <date>
|
|
|
376
374
|
```
|
|
377
375
|
|
|
378
376
|
|
|
377
|
+
</output>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"name": "{{SCOPE}}",
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
|
+
"workspaces": ["apps/*", "apis/*", "packages/*", "config/*"],
|
|
5
6
|
"scripts": {
|
|
6
7
|
"dev": "turbo run dev",
|
|
7
8
|
"build": "turbo run build",
|
|
@@ -18,7 +19,6 @@
|
|
|
18
19
|
"turbo": "^2",
|
|
19
20
|
"typescript": "^5.9"
|
|
20
21
|
},
|
|
21
|
-
"packageManager": "pnpm@9.12.2",
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=22"
|
|
24
24
|
}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import pc4 from "picocolors";
|
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
var API_URL = process.env["WITHMATA_API_URL"] || "https://blueprints.withmata.dev";
|
|
9
|
-
var VERSION = "0.
|
|
9
|
+
var VERSION = "0.5.0";
|
|
10
10
|
|
|
11
11
|
// src/lib/install-state.ts
|
|
12
12
|
import { existsSync, readdirSync, lstatSync, readlinkSync } from "fs";
|
|
@@ -430,19 +430,28 @@ var SKILL_GROUPS = [
|
|
|
430
430
|
]
|
|
431
431
|
},
|
|
432
432
|
{
|
|
433
|
-
title: "
|
|
433
|
+
title: "Marketing & Design",
|
|
434
434
|
skills: [
|
|
435
435
|
{
|
|
436
|
-
command: "/
|
|
437
|
-
description: "
|
|
436
|
+
command: "/copywrite",
|
|
437
|
+
description: "Write conversion-focused marketing page copy (pricing, about, features)"
|
|
438
438
|
},
|
|
439
439
|
{
|
|
440
|
-
command: "/
|
|
441
|
-
description: "
|
|
440
|
+
command: "/copywrite-landing",
|
|
441
|
+
description: "Write high-converting landing page copy with CRO frameworks"
|
|
442
442
|
},
|
|
443
443
|
{
|
|
444
|
-
command: "/
|
|
445
|
-
description: "
|
|
444
|
+
command: "/design-system",
|
|
445
|
+
description: "Define visual identity and generate custom Tailwind v4 tokens"
|
|
446
|
+
}
|
|
447
|
+
]
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
title: "Scaffold Your Project",
|
|
451
|
+
skills: [
|
|
452
|
+
{
|
|
453
|
+
command: "/scaffold-foundation",
|
|
454
|
+
description: "Set up a monorepo with Turborepo and shared tooling"
|
|
446
455
|
},
|
|
447
456
|
{
|
|
448
457
|
command: "/scaffold-env",
|
|
@@ -450,11 +459,19 @@ var SKILL_GROUPS = [
|
|
|
450
459
|
},
|
|
451
460
|
{
|
|
452
461
|
command: "/scaffold-tailwind",
|
|
453
|
-
description: "Add
|
|
462
|
+
description: "Add Tailwind v4 design system (auto-detects custom design tokens)"
|
|
454
463
|
},
|
|
455
464
|
{
|
|
456
465
|
command: "/scaffold-ui",
|
|
457
|
-
description: "Add
|
|
466
|
+
description: "Add 31 Base UI components with Phosphor icons and form system"
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
command: "/scaffold-db",
|
|
470
|
+
description: "Add a database with Drizzle ORM and PostgreSQL"
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
command: "/scaffold-auth",
|
|
474
|
+
description: "Add authentication, sessions, and organization support"
|
|
458
475
|
}
|
|
459
476
|
]
|
|
460
477
|
},
|
package/package.json
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{SCOPE}}/tailwind-config",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"private": true,
|
|
6
|
-
"exports": {
|
|
7
|
-
".": "./shared-styles.css",
|
|
8
|
-
"./postcss": "./postcss.config.mjs"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"@tailwindcss/typography": "^0.5",
|
|
12
|
-
"shadcn": "^3",
|
|
13
|
-
"tailwind-scrollbar-hide": "^4",
|
|
14
|
-
"tw-animate-css": "^1"
|
|
15
|
-
},
|
|
16
|
-
"devDependencies": {
|
|
17
|
-
"@tailwindcss/postcss": "^4",
|
|
18
|
-
"tailwindcss": "^4"
|
|
19
|
-
}
|
|
20
|
-
}
|