@folpe/loom 0.2.0 → 0.3.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.
Files changed (45) hide show
  1. package/README.md +82 -16
  2. package/data/agents/backend/AGENT.md +12 -0
  3. package/data/agents/database/AGENT.md +3 -0
  4. package/data/agents/frontend/AGENT.md +10 -0
  5. package/data/agents/marketing/AGENT.md +3 -0
  6. package/data/agents/orchestrator/AGENT.md +1 -19
  7. package/data/agents/security/AGENT.md +3 -0
  8. package/data/agents/tests/AGENT.md +2 -0
  9. package/data/agents/ux-ui/AGENT.md +5 -0
  10. package/data/presets/api-backend.yaml +0 -3
  11. package/data/presets/chrome-extension.yaml +0 -3
  12. package/data/presets/cli-tool.yaml +0 -3
  13. package/data/presets/e-commerce.yaml +0 -3
  14. package/data/presets/expo-mobile.yaml +0 -3
  15. package/data/presets/fullstack-auth.yaml +0 -3
  16. package/data/presets/landing-page.yaml +0 -3
  17. package/data/presets/mvp-lean.yaml +0 -3
  18. package/data/presets/saas-default.yaml +3 -4
  19. package/data/presets/saas-full.yaml +71 -0
  20. package/data/skills/api-design/SKILL.md +43 -2
  21. package/data/skills/auth-rbac/SKILL.md +179 -0
  22. package/data/skills/better-auth-patterns/SKILL.md +212 -0
  23. package/data/skills/chrome-extension-patterns/SKILL.md +13 -6
  24. package/data/skills/cli-development/SKILL.md +11 -3
  25. package/data/skills/drizzle-patterns/SKILL.md +166 -0
  26. package/data/skills/env-validation/SKILL.md +142 -0
  27. package/data/skills/form-validation/SKILL.md +169 -0
  28. package/data/skills/hero-copywriting/SKILL.md +12 -4
  29. package/data/skills/i18n-patterns/SKILL.md +176 -0
  30. package/data/skills/layered-architecture/SKILL.md +131 -0
  31. package/data/skills/nextjs-conventions/SKILL.md +46 -7
  32. package/data/skills/react-native-patterns/SKILL.md +10 -8
  33. package/data/skills/react-query-patterns/SKILL.md +193 -0
  34. package/data/skills/resend-email/SKILL.md +181 -0
  35. package/data/skills/seo-optimization/SKILL.md +10 -2
  36. package/data/skills/server-actions-patterns/SKILL.md +156 -0
  37. package/data/skills/shadcn-ui/SKILL.md +46 -12
  38. package/data/skills/stripe-integration/SKILL.md +11 -3
  39. package/data/skills/supabase-patterns/SKILL.md +23 -6
  40. package/data/skills/table-pagination/SKILL.md +224 -0
  41. package/data/skills/tailwind-patterns/SKILL.md +12 -2
  42. package/data/skills/testing-patterns/SKILL.md +203 -0
  43. package/data/skills/ui-ux-guidelines/SKILL.md +10 -5
  44. package/dist/index.js +254 -100
  45. package/package.json +2 -1
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: react-query-patterns
3
+ description: "TanStack React Query for data fetching, caching, mutations, and optimistic updates. Use when managing server state in client components, implementing data fetching hooks, or caching API responses."
4
+ ---
5
+
6
+ # React Query Patterns
7
+
8
+ ## Critical Rules
9
+
10
+ - **Query keys are structured** — use the factory pattern from `query-keys.ts`.
11
+ - **Custom hooks for reuse** — wrap `useQuery`/`useMutation` in domain hooks.
12
+ - **Invalidate on mutation** — always invalidate related queries after writes.
13
+ - **Optimistic updates for UX** — use for edits and deletes where latency matters.
14
+ - **Prefetch in RSC** — hydrate queries in server components for instant loads.
15
+ - **Never fetch in useEffect** — use React Query instead.
16
+
17
+ ## Setup
18
+
19
+ ```tsx
20
+ // src/providers/query-provider.tsx
21
+ "use client";
22
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
23
+ import { useState } from "react";
24
+
25
+ export function QueryProvider({ children }: { children: React.ReactNode }) {
26
+ const [queryClient] = useState(
27
+ () =>
28
+ new QueryClient({
29
+ defaultOptions: {
30
+ queries: {
31
+ staleTime: 60 * 1000, // 1 minute
32
+ refetchOnWindowFocus: false,
33
+ },
34
+ },
35
+ })
36
+ );
37
+
38
+ return (
39
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
40
+ );
41
+ }
42
+ ```
43
+
44
+ ## Query Keys Convention
45
+
46
+ ```ts
47
+ // src/lib/query-keys.ts
48
+ export const queryKeys = {
49
+ users: {
50
+ all: ["users"] as const,
51
+ list: (filters: UserFilters) => ["users", "list", filters] as const,
52
+ detail: (id: string) => ["users", "detail", id] as const,
53
+ },
54
+ posts: {
55
+ all: ["posts"] as const,
56
+ list: (filters: PostFilters) => ["posts", "list", filters] as const,
57
+ detail: (id: string) => ["posts", "detail", id] as const,
58
+ comments: (postId: string) => ["posts", postId, "comments"] as const,
59
+ },
60
+ } as const;
61
+ ```
62
+
63
+ ## Queries
64
+
65
+ ### Basic Query
66
+
67
+ ```tsx
68
+ "use client";
69
+ import { useQuery } from "@tanstack/react-query";
70
+ import { queryKeys } from "@/lib/query-keys";
71
+
72
+ export function UserList() {
73
+ const { data: users, isLoading, error } = useQuery({
74
+ queryKey: queryKeys.users.all,
75
+ queryFn: () => fetch("/api/users").then((r) => r.json()),
76
+ });
77
+
78
+ if (isLoading) return <Skeleton />;
79
+ if (error) return <ErrorMessage error={error} />;
80
+
81
+ return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
82
+ }
83
+ ```
84
+
85
+ ### Query with Server Action
86
+
87
+ ```tsx
88
+ import { useQuery } from "@tanstack/react-query";
89
+ import { getUserList } from "@/actions/user.actions";
90
+
91
+ const { data } = useQuery({
92
+ queryKey: queryKeys.users.list(filters),
93
+ queryFn: () => getUserList(filters),
94
+ });
95
+ ```
96
+
97
+ ## Mutations
98
+
99
+ ### Basic Mutation
100
+
101
+ ```tsx
102
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
103
+ import { createUser } from "@/actions/user.actions";
104
+ import { queryKeys } from "@/lib/query-keys";
105
+ import { toast } from "sonner";
106
+
107
+ export function useCreateUser() {
108
+ const queryClient = useQueryClient();
109
+
110
+ return useMutation({
111
+ mutationFn: createUser,
112
+ onSuccess: () => {
113
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
114
+ toast.success("User created");
115
+ },
116
+ onError: (error) => {
117
+ toast.error(error.message);
118
+ },
119
+ });
120
+ }
121
+ ```
122
+
123
+ ### Optimistic Update
124
+
125
+ ```tsx
126
+ export function useUpdateUser() {
127
+ const queryClient = useQueryClient();
128
+
129
+ return useMutation({
130
+ mutationFn: ({ id, data }: { id: string; data: UpdateUserInput }) =>
131
+ updateUser(id, data),
132
+ onMutate: async ({ id, data }) => {
133
+ await queryClient.cancelQueries({ queryKey: queryKeys.users.detail(id) });
134
+ const previous = queryClient.getQueryData(queryKeys.users.detail(id));
135
+ queryClient.setQueryData(queryKeys.users.detail(id), (old: User) => ({
136
+ ...old,
137
+ ...data,
138
+ }));
139
+ return { previous };
140
+ },
141
+ onError: (_err, { id }, context) => {
142
+ queryClient.setQueryData(queryKeys.users.detail(id), context?.previous);
143
+ },
144
+ onSettled: (_data, _err, { id }) => {
145
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.detail(id) });
146
+ },
147
+ });
148
+ }
149
+ ```
150
+
151
+ ## Custom Hooks
152
+
153
+ ```ts
154
+ // src/hooks/use-users.ts
155
+ export function useUsers(filters?: UserFilters) {
156
+ return useQuery({
157
+ queryKey: queryKeys.users.list(filters ?? {}),
158
+ queryFn: () => getUserList(filters),
159
+ });
160
+ }
161
+
162
+ export function useUser(id: string) {
163
+ return useQuery({
164
+ queryKey: queryKeys.users.detail(id),
165
+ queryFn: () => getUserById(id),
166
+ enabled: !!id,
167
+ });
168
+ }
169
+ ```
170
+
171
+ ## Prefetching with RSC
172
+
173
+ ```tsx
174
+ // app/users/page.tsx (Server Component)
175
+ import { HydrationBoundary, dehydrate, QueryClient } from "@tanstack/react-query";
176
+ import { getUserList } from "@/actions/user.actions";
177
+ import { queryKeys } from "@/lib/query-keys";
178
+ import { UserList } from "./user-list";
179
+
180
+ export default async function UsersPage() {
181
+ const queryClient = new QueryClient();
182
+ await queryClient.prefetchQuery({
183
+ queryKey: queryKeys.users.all,
184
+ queryFn: getUserList,
185
+ });
186
+
187
+ return (
188
+ <HydrationBoundary state={dehydrate(queryClient)}>
189
+ <UserList />
190
+ </HydrationBoundary>
191
+ );
192
+ }
193
+ ```
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: resend-email
3
+ description: "Email sending patterns with Resend and React Email templates. Use when implementing transactional emails, creating email templates, or adding i18n email support."
4
+ ---
5
+
6
+ # Resend Email Patterns
7
+
8
+ ## Critical Rules
9
+
10
+ - **Emails via service layer** — never send directly from routes.
11
+ - **React Email for templates** — type-safe, previewable components.
12
+ - **I18n support** — every email must support multiple locales.
13
+ - **Inline styles only** — CSS classes don't work in most email clients.
14
+ - **Handle delivery webhooks** — bounces and complaints must be processed.
15
+ - **Preview emails locally** — use `email dev` command during development.
16
+
17
+ ## Setup
18
+
19
+ ```ts
20
+ // src/lib/resend.ts
21
+ import { Resend } from "resend";
22
+
23
+ export const resend = new Resend(process.env.RESEND_API_KEY);
24
+ ```
25
+
26
+ ## Email Service Layer
27
+
28
+ - Emails are sent from a dedicated service — never from routes or components directly.
29
+ - Located in `src/services/email.service.ts`.
30
+
31
+ ```ts
32
+ // src/services/email.service.ts
33
+ import { resend } from "@/lib/resend";
34
+ import { WelcomeEmail } from "@/emails/welcome";
35
+ import { InviteEmail } from "@/emails/invite";
36
+
37
+ const FROM = "App Name <noreply@yourdomain.com>";
38
+
39
+ export async function sendWelcomeEmail(to: string, name: string, locale: string) {
40
+ return resend.emails.send({
41
+ from: FROM,
42
+ to,
43
+ subject: getSubject("welcome", locale),
44
+ react: WelcomeEmail({ name, locale }),
45
+ });
46
+ }
47
+
48
+ export async function sendInviteEmail(
49
+ to: string,
50
+ inviterName: string,
51
+ orgName: string,
52
+ inviteUrl: string,
53
+ locale: string
54
+ ) {
55
+ return resend.emails.send({
56
+ from: FROM,
57
+ to,
58
+ subject: getSubject("invite", locale, { orgName }),
59
+ react: InviteEmail({ inviterName, orgName, inviteUrl, locale }),
60
+ });
61
+ }
62
+ ```
63
+
64
+ ## React Email Templates
65
+
66
+ ```tsx
67
+ // src/emails/welcome.tsx
68
+ import {
69
+ Html, Head, Body, Container, Section, Text, Button, Hr,
70
+ } from "@react-email/components";
71
+ import { getEmailTranslations } from "@/lib/email-i18n";
72
+
73
+ interface WelcomeEmailProps {
74
+ name: string;
75
+ locale: string;
76
+ }
77
+
78
+ export function WelcomeEmail({ name, locale }: WelcomeEmailProps) {
79
+ const t = getEmailTranslations(locale, "welcome");
80
+
81
+ return (
82
+ <Html>
83
+ <Head />
84
+ <Body style={body}>
85
+ <Container style={container}>
86
+ <Text style={heading}>{t("title", { name })}</Text>
87
+ <Text style={paragraph}>{t("description")}</Text>
88
+ <Section style={buttonSection}>
89
+ <Button style={button} href="https://app.yourdomain.com/dashboard">
90
+ {t("cta")}
91
+ </Button>
92
+ </Section>
93
+ <Hr style={hr} />
94
+ <Text style={footer}>{t("footer")}</Text>
95
+ </Container>
96
+ </Body>
97
+ </Html>
98
+ );
99
+ }
100
+
101
+ // Inline styles for email compatibility
102
+ const body = { backgroundColor: "#f6f9fc", fontFamily: "sans-serif" };
103
+ const container = { margin: "0 auto", padding: "40px 20px", maxWidth: "560px" };
104
+ const heading = { fontSize: "24px", fontWeight: "bold", marginBottom: "16px" };
105
+ const paragraph = { fontSize: "16px", lineHeight: "1.5", color: "#333" };
106
+ const buttonSection = { textAlign: "center" as const, margin: "32px 0" };
107
+ const button = {
108
+ backgroundColor: "#000", color: "#fff", padding: "12px 24px",
109
+ borderRadius: "6px", fontSize: "16px", textDecoration: "none",
110
+ };
111
+ const hr = { borderColor: "#e6ebf1", margin: "32px 0" };
112
+ const footer = { fontSize: "12px", color: "#8898aa" };
113
+ ```
114
+
115
+ ## I18n for Emails
116
+
117
+ ```ts
118
+ // src/lib/email-i18n.ts
119
+ const emailMessages: Record<string, Record<string, Record<string, string>>> = {
120
+ en: {
121
+ welcome: {
122
+ title: "Welcome, {name}!",
123
+ description: "We're excited to have you on board.",
124
+ cta: "Go to Dashboard",
125
+ footer: "You received this email because you created an account.",
126
+ },
127
+ invite: {
128
+ title: "{inviterName} invited you to {orgName}",
129
+ description: "Click below to accept the invitation.",
130
+ cta: "Accept Invitation",
131
+ },
132
+ },
133
+ fr: {
134
+ welcome: {
135
+ title: "Bienvenue, {name} !",
136
+ description: "Nous sommes ravis de vous compter parmi nous.",
137
+ cta: "Aller au tableau de bord",
138
+ footer: "Vous avez reçu cet e-mail car vous avez créé un compte.",
139
+ },
140
+ },
141
+ };
142
+
143
+ export function getEmailTranslations(locale: string, namespace: string) {
144
+ const messages = emailMessages[locale]?.[namespace] ?? emailMessages.en[namespace];
145
+
146
+ return (key: string, params?: Record<string, string>) => {
147
+ let message = messages[key] ?? key;
148
+ if (params) {
149
+ for (const [k, v] of Object.entries(params)) {
150
+ message = message.replace(`{${k}}`, v);
151
+ }
152
+ }
153
+ return message;
154
+ };
155
+ }
156
+ ```
157
+
158
+ ## Webhook for Delivery Status
159
+
160
+ ```ts
161
+ // src/app/api/webhook/resend/route.ts
162
+ import { NextResponse } from "next/server";
163
+
164
+ export async function POST(request: Request) {
165
+ const payload = await request.json();
166
+
167
+ switch (payload.type) {
168
+ case "email.delivered":
169
+ // Log successful delivery
170
+ break;
171
+ case "email.bounced":
172
+ // Mark email as bounced, disable future sends
173
+ break;
174
+ case "email.complained":
175
+ // Unsubscribe user
176
+ break;
177
+ }
178
+
179
+ return NextResponse.json({ received: true });
180
+ }
181
+ ```
@@ -1,11 +1,19 @@
1
1
  ---
2
2
  name: seo-optimization
3
- description: "SEO best practices for structured data, meta tags, Core Web Vitals, and indexing. Use when building landing pages, marketing sites, or public-facing content."
4
- allowed-tools: "Read, Write, Edit, Glob, Grep"
3
+ description: "SEO best practices for structured data, meta tags, Core Web Vitals, and indexing. Use when building landing pages, optimizing page metadata, adding JSON-LD structured data, generating sitemaps, or improving search engine performance."
5
4
  ---
6
5
 
7
6
  # SEO Optimization
8
7
 
8
+ ## Critical Rules
9
+
10
+ - **Every page must have unique `title` and `description`** — never duplicate meta tags.
11
+ - **One `<h1>` per page** — use proper heading hierarchy `h1` -> `h2` -> `h3`.
12
+ - **Use semantic HTML** — `<main>`, `<article>`, `<section>`, `<nav>`, `<aside>`, `<footer>`.
13
+ - **All images must have `alt` text** — descriptive, not keyword-stuffed.
14
+ - **Target Core Web Vitals** — LCP < 2.5s, INP < 200ms, CLS < 0.1.
15
+ - **Generate `sitemap.xml` and `robots.txt`** — never leave them missing on public sites.
16
+
9
17
  ## Meta Tags
10
18
 
11
19
  - Every page must have unique `title` and `description` meta tags:
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: server-actions-patterns
3
+ description: "Next.js Server Actions with safe wrappers, validation, and error handling. Use when implementing mutations, creating form handlers, or building 'use server' functions."
4
+ ---
5
+
6
+ # Server Actions Patterns
7
+
8
+ ## Critical Rules
9
+
10
+ - **Always validate inputs** — never trust client data.
11
+ - **Always check auth** — every action must verify the user session.
12
+ - **Never expose internal errors** — return generic messages to the client.
13
+ - **Revalidate after mutations** — stale UI is a bug.
14
+ - **Keep actions thin** — delegate to facades/services for business logic.
15
+ - **One action per mutation** — avoid multi-purpose actions.
16
+ - **Never import server-only code in client files** — pass actions via props or use wrappers.
17
+
18
+ ## File Organization
19
+
20
+ - Server Actions are defined in `src/actions/` directory.
21
+ - One file per domain entity: `src/actions/{entity}.actions.ts`.
22
+ - Every action file starts with `"use server"` directive at the top.
23
+
24
+ ```
25
+ src/actions/
26
+ user.actions.ts
27
+ post.actions.ts
28
+ organization.actions.ts
29
+ ```
30
+
31
+ ## Safe Server Action Wrapper
32
+
33
+ Use a `safeAction` wrapper for consistent validation, auth, and error handling:
34
+
35
+ ```ts
36
+ // src/lib/safe-action.ts
37
+ "use server";
38
+ import { z } from "zod";
39
+ import { getCurrentUser } from "@/lib/auth";
40
+
41
+ type ActionResult<T> = { success: true; data: T } | { success: false; error: string };
42
+
43
+ export function createSafeAction<TInput extends z.ZodType, TOutput>(
44
+ schema: TInput,
45
+ handler: (input: z.infer<TInput>, user: AuthUser) => Promise<TOutput>
46
+ ) {
47
+ return async (input: z.infer<TInput>): Promise<ActionResult<TOutput>> => {
48
+ try {
49
+ const user = await getCurrentUser();
50
+ if (!user) return { success: false, error: "Unauthorized" };
51
+
52
+ const parsed = schema.safeParse(input);
53
+ if (!parsed.success) {
54
+ return { success: false, error: parsed.error.errors[0].message };
55
+ }
56
+
57
+ const data = await handler(parsed.data, user);
58
+ return { success: true, data };
59
+ } catch (error) {
60
+ console.error("Action error:", error);
61
+ return { success: false, error: "An unexpected error occurred" };
62
+ }
63
+ };
64
+ }
65
+ ```
66
+
67
+ ## Action Implementation
68
+
69
+ ```ts
70
+ // src/actions/user.actions.ts
71
+ "use server";
72
+
73
+ import { z } from "zod";
74
+ import { revalidatePath } from "next/cache";
75
+ import { createSafeAction } from "@/lib/safe-action";
76
+ import { updateUserProfile } from "@/facades/user.facade";
77
+
78
+ const UpdateProfileSchema = z.object({
79
+ name: z.string().min(2).max(100),
80
+ bio: z.string().max(500).optional(),
81
+ });
82
+
83
+ export const updateProfile = createSafeAction(
84
+ UpdateProfileSchema,
85
+ async (input, user) => {
86
+ const result = await updateUserProfile(user.id, input);
87
+ revalidatePath("/profile");
88
+ return result;
89
+ }
90
+ );
91
+ ```
92
+
93
+ ## Server-Only Modules
94
+
95
+ - Mark server-only modules with the `server-only` package:
96
+ ```ts
97
+ import "server-only";
98
+ ```
99
+ - Use `"use server"` only in action files — never in utility or service files.
100
+
101
+ ## Integration with Forms
102
+
103
+ ### With useActionState (React 19)
104
+
105
+ ```tsx
106
+ "use client";
107
+ import { useActionState } from "react";
108
+ import { updateProfile } from "@/actions/user.actions";
109
+
110
+ export function ProfileForm() {
111
+ const [state, formAction, isPending] = useActionState(updateProfile, null);
112
+
113
+ return (
114
+ <form action={formAction}>
115
+ <input name="name" />
116
+ {state?.error && <p className="text-destructive">{state.error}</p>}
117
+ <button type="submit" disabled={isPending}>
118
+ {isPending ? "Saving..." : "Save"}
119
+ </button>
120
+ </form>
121
+ );
122
+ }
123
+ ```
124
+
125
+ ### With react-hook-form
126
+
127
+ ```tsx
128
+ "use client";
129
+ import { useForm } from "react-hook-form";
130
+ import { zodResolver } from "@hookform/resolvers/zod";
131
+ import { useTransition } from "react";
132
+ import { updateProfile } from "@/actions/user.actions";
133
+ import { toast } from "sonner";
134
+
135
+ export function ProfileForm() {
136
+ const [isPending, startTransition] = useTransition();
137
+ const form = useForm({ resolver: zodResolver(UpdateProfileSchema) });
138
+
139
+ const onSubmit = form.handleSubmit((data) => {
140
+ startTransition(async () => {
141
+ const result = await updateProfile(data);
142
+ if (result.success) toast.success("Profile updated");
143
+ else toast.error(result.error);
144
+ });
145
+ });
146
+
147
+ return <form onSubmit={onSubmit}>{/* fields */}</form>;
148
+ }
149
+ ```
150
+
151
+ ## Revalidation
152
+
153
+ - Always call `revalidatePath()` or `revalidateTag()` after mutations.
154
+ - Use path revalidation for simple cases: `revalidatePath("/users")`.
155
+ - Use tag revalidation for granular cache control: `revalidateTag("user-list")`.
156
+ - Redirect with `redirect()` after create operations when appropriate.
@@ -1,11 +1,19 @@
1
1
  ---
2
2
  name: shadcn-ui
3
- description: "ShadCN UI component patterns, composition, and accessibility guidelines. Use when building interfaces with ShadCN components. Inspired by ibelick/ui-skills baseline-ui."
4
- allowed-tools: "Read, Write, Edit, Glob, Grep"
3
+ description: "ShadCN UI component patterns, forms, data tables, and page layouts. Use when building interfaces with ShadCN components, creating forms with react-hook-form, tables with TanStack Table, or designing page layouts."
5
4
  ---
6
5
 
7
6
  # ShadCN UI Patterns
8
7
 
8
+ ## Critical Rules
9
+
10
+ - **Always use ShadCN primitives first** — before building custom components.
11
+ - **Never rebuild keyboard or focus behavior** — use the component primitives.
12
+ - **Never mix primitive systems** — don't combine Radix, Headless UI, React Aria in the same surface.
13
+ - **Never use `h-screen`** — use `h-dvh` for correct mobile viewport.
14
+ - **Empty states must have one clear next action** — never blank screens.
15
+ - **No gradients or glow effects** unless explicitly requested.
16
+
9
17
  ## Installation & Setup
10
18
 
11
19
  - Install components individually with `npx shadcn@latest add <component>` — never install all at once.
@@ -27,8 +35,6 @@ allowed-tools: "Read, Write, Edit, Glob, Grep"
27
35
  - `Badge` for tags and status indicators
28
36
  - `Tabs` for content switching
29
37
  - `Toast` / `Sonner` for notifications
30
- - Never rebuild keyboard or focus behavior by hand — use the component primitives.
31
- - Never mix primitive systems (Radix, Headless UI, React Aria) within the same surface.
32
38
 
33
39
  ## Forms
34
40
 
@@ -52,6 +58,8 @@ allowed-tools: "Read, Write, Edit, Glob, Grep"
52
58
  - Add row actions via `DropdownMenu`.
53
59
  - Use `tabular-nums` on numeric columns for alignment.
54
60
  - Add loading skeletons with ShadCN `Skeleton` for async data.
61
+ - **Toolbar pattern**: search input + result counter + limit selector in every table.
62
+ - Hide columns progressively by breakpoint: `hidden sm:table-cell`, `hidden md:table-cell`.
55
63
 
56
64
  ## Dialogs & Alerts
57
65
 
@@ -60,6 +68,40 @@ allowed-tools: "Read, Write, Edit, Glob, Grep"
60
68
  - Keep dialog content focused — one primary action per dialog.
61
69
  - Always provide a way to dismiss (close button, escape key, outside click).
62
70
 
71
+ ## Page Layout Pattern
72
+
73
+ Structure pages with Cards for consistent visual hierarchy:
74
+
75
+ ```tsx
76
+ // Header → Cards → Footer pattern
77
+ <div className="space-y-6">
78
+ <div className="flex items-center justify-between">
79
+ <h1 className="text-3xl font-bold tracking-tight">Page Title</h1>
80
+ <Button>Primary Action</Button>
81
+ </div>
82
+
83
+ <Card>
84
+ <CardHeader><CardTitle>Section</CardTitle></CardHeader>
85
+ <CardContent>{/* content */}</CardContent>
86
+ </Card>
87
+ </div>
88
+ ```
89
+
90
+ - Multi-Card forms: split complex forms into logical Card sections.
91
+ - Use `CardHeader` + `CardTitle` + `CardDescription` for section context.
92
+
93
+ ## Charts
94
+
95
+ - Use `ChartContainer` from ShadCN with Recharts for data visualization.
96
+ - Wrap charts in a `Card` with descriptive `CardHeader`.
97
+ - Always include a `ChartTooltip` for data point details.
98
+
99
+ ## File Upload
100
+
101
+ - Use a `FileUpload` dropzone component with drag-and-drop support.
102
+ - Show preview for images, file name + size for documents.
103
+ - Validate file type and size client-side before upload.
104
+
63
105
  ## Theming
64
106
 
65
107
  - Use CSS variables from the ShadCN theme system: `bg-background`, `text-foreground`, `bg-card`, `text-muted-foreground`, `bg-primary`, etc.
@@ -82,11 +124,3 @@ allowed-tools: "Read, Write, Edit, Glob, Grep"
82
124
  - Keep interaction feedback under 200ms.
83
125
  - Avoid animating layout properties (`width`, `height`, `margin`, `padding`) — use `transform` and `opacity` only.
84
126
  - Respect `prefers-reduced-motion` media query.
85
-
86
- ## Anti-Patterns to Avoid
87
-
88
- - Never use `h-screen` — use `h-dvh` for correct mobile viewport.
89
- - Never use arbitrary `z-index` values — use a fixed scale.
90
- - Never use gradients or glow effects unless explicitly requested.
91
- - Never use custom easing curves unless explicitly requested.
92
- - Empty states must have one clear next action.
@@ -1,11 +1,19 @@
1
1
  ---
2
2
  name: stripe-integration
3
- description: "Stripe payment integration patterns for checkout, subscriptions, and webhooks. Use when building e-commerce, SaaS billing, or any payment flow."
4
- allowed-tools: "Read, Write, Edit, Glob, Grep"
3
+ description: "Stripe payment integration patterns for checkout, subscriptions, webhooks, and customer portal. Use when implementing payments, building SaaS billing, handling Stripe webhooks, creating checkout flows, or managing subscription lifecycle."
5
4
  ---
6
5
 
7
6
  # Stripe Integration
8
7
 
8
+ ## Critical Rules
9
+
10
+ - **Always use Stripe Checkout or Payment Elements** — never collect card details directly.
11
+ - **Always verify webhook signatures** — never trust unverified payloads.
12
+ - **Never expose `STRIPE_SECRET_KEY` to the client** — server-side only.
13
+ - **Make webhook handlers idempotent** — the same event may arrive multiple times.
14
+ - **Use `metadata` to link Stripe objects to database records** — always pass `userId`, `orderId`.
15
+ - **Use `idempotencyKey` for critical operations** — prevent duplicate charges.
16
+
9
17
  ## Setup
10
18
 
11
19
  - Install `stripe` (server) and `@stripe/stripe-js` + `@stripe/react-stripe-js` (client).
@@ -21,7 +29,7 @@ allowed-tools: "Read, Write, Edit, Glob, Grep"
21
29
 
22
30
  ## Checkout
23
31
 
24
- - **Always use Stripe Checkout or Payment Elements** — never collect card details directly.
32
+ - Always use Stripe Checkout or Payment Elements — never collect card details directly.
25
33
  - Create a Checkout Session server-side, redirect client-side:
26
34
  ```ts
27
35
  const session = await stripe.checkout.sessions.create({