create-edhor-stack 0.1.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/LICENSE +21 -0
- package/README.md +75 -0
- package/STACK.md +1086 -0
- package/dist/index.js +3181 -0
- package/package.json +44 -0
- package/templates/apps/api-elysia/package.json +21 -0
- package/templates/apps/api-elysia/src/index.ts +59 -0
- package/templates/apps/api-elysia/src/lib/eden.ts +25 -0
- package/templates/apps/api-elysia/src/lib/env.ts +18 -0
- package/templates/apps/api-elysia/src/routes/health.ts +13 -0
- package/templates/apps/api-elysia/src/routes/users.ts +117 -0
- package/templates/apps/api-elysia/tsconfig.json +15 -0
- package/templates/apps/api-hono/package.json +20 -0
- package/templates/apps/api-hono/src/index.ts +66 -0
- package/templates/apps/api-hono/src/lib/env.ts +18 -0
- package/templates/apps/api-hono/src/routes/health.ts +20 -0
- package/templates/apps/api-hono/src/routes/users.ts +110 -0
- package/templates/apps/api-hono/tsconfig.json +15 -0
- package/templates/apps/mobile/.env.example +9 -0
- package/templates/apps/mobile/app/_layout.tsx +16 -0
- package/templates/apps/mobile/app/index.tsx +39 -0
- package/templates/apps/mobile/app.json +37 -0
- package/templates/apps/mobile/assets/adaptive-icon.png +0 -0
- package/templates/apps/mobile/assets/favicon.png +0 -0
- package/templates/apps/mobile/assets/icon.png +0 -0
- package/templates/apps/mobile/assets/splash-icon.png +0 -0
- package/templates/apps/mobile/package.json +39 -0
- package/templates/apps/mobile/src/api/client.ts +51 -0
- package/templates/apps/mobile/src/api/index.ts +3 -0
- package/templates/apps/mobile/src/api/queries.ts +24 -0
- package/templates/apps/mobile/src/api/schemas.ts +32 -0
- package/templates/apps/mobile/src/lib/env.ts +40 -0
- package/templates/apps/mobile/src/lib/query-client.ts +28 -0
- package/templates/apps/mobile/src/lib/result.ts +45 -0
- package/templates/apps/mobile/src/lib/store.ts +63 -0
- package/templates/apps/mobile/tsconfig.json +10 -0
- package/templates/apps/web/.env.example +11 -0
- package/templates/apps/web/package.json +29 -0
- package/templates/apps/web/src/lib/env.ts +52 -0
- package/templates/apps/web/src/lib/queries.ts +27 -0
- package/templates/apps/web/src/lib/query-client.ts +11 -0
- package/templates/apps/web/src/router.tsx +17 -0
- package/templates/apps/web/src/routes/__root.tsx +32 -0
- package/templates/apps/web/src/routes/index.tsx +16 -0
- package/templates/apps/web/src/styles.css +26 -0
- package/templates/apps/web/tsconfig.json +10 -0
- package/templates/apps/web/vite.config.ts +21 -0
- package/templates/base/.claude/settings.json +33 -0
- package/templates/base/.claude/skills/add-api-endpoint.md +137 -0
- package/templates/base/.claude/skills/add-component.md +79 -0
- package/templates/base/.claude/skills/add-route.md +134 -0
- package/templates/base/.claude/skills/add-store.md +158 -0
- package/templates/base/.husky/pre-commit +1 -0
- package/templates/base/.lintstagedrc +4 -0
- package/templates/base/.node-version +1 -0
- package/templates/base/AGENTS.md +135 -0
- package/templates/base/CLAUDE.md.hbs +139 -0
- package/templates/base/Dockerfile +32 -0
- package/templates/base/biome.json +52 -0
- package/templates/base/fly.toml.hbs +20 -0
- package/templates/base/gitignore +36 -0
- package/templates/base/package.json.hbs +22 -0
- package/templates/base/tsconfig.json +14 -0
- package/templates/base/turbo.json +22 -0
- package/templates/packages/shared/package.json +17 -0
- package/templates/packages/shared/src/index.ts +4 -0
- package/templates/packages/shared/src/schemas.ts +50 -0
- package/templates/packages/shared/src/types.ts +47 -0
- package/templates/packages/shared/src/utils.ts +87 -0
- package/templates/packages/shared/tsconfig.json +14 -0
- package/templates/packages/stripe/package.json +18 -0
- package/templates/packages/stripe/src/client.ts +110 -0
- package/templates/packages/stripe/src/index.ts +3 -0
- package/templates/packages/stripe/src/schemas.ts +65 -0
- package/templates/packages/stripe/src/webhooks.ts +91 -0
- package/templates/packages/stripe/tsconfig.json +14 -0
- package/templates/packages/ui/components.json +19 -0
- package/templates/packages/ui/package.json +29 -0
- package/templates/packages/ui/src/components/button.tsx +58 -0
- package/templates/packages/ui/src/index.ts +5 -0
- package/templates/packages/ui/src/lib/utils.ts +6 -0
- package/templates/packages/ui/src/styles.css +120 -0
- package/templates/packages/ui/tsconfig.json +10 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-api-endpoint
|
|
3
|
+
description: Create a new API endpoint with Zod validation
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add API Endpoint
|
|
7
|
+
|
|
8
|
+
Creates a type-safe API endpoint with Zod schema validation.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/add-api-endpoint users
|
|
14
|
+
/add-api-endpoint "projects/[id]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Process
|
|
18
|
+
|
|
19
|
+
### 1. Define Schema
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// src/api/schemas.ts (or add to existing)
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
|
|
25
|
+
export const UserSchema = z.object({
|
|
26
|
+
id: z.string(),
|
|
27
|
+
email: z.string().email(),
|
|
28
|
+
name: z.string(),
|
|
29
|
+
createdAt: z.string().datetime(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export type User = z.infer<typeof UserSchema>;
|
|
33
|
+
|
|
34
|
+
export const UsersResponseSchema = z.object({
|
|
35
|
+
users: z.array(UserSchema),
|
|
36
|
+
total: z.number(),
|
|
37
|
+
nextCursor: z.string().nullable(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export type UsersResponse = z.infer<typeof UsersResponseSchema>;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Create Query Options
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// src/api/queries.ts (or src/lib/queries.ts for web)
|
|
47
|
+
import { queryOptions } from '@tanstack/react-query';
|
|
48
|
+
import { fetchValidated } from './client';
|
|
49
|
+
import { UserSchema, UsersResponseSchema } from './schemas';
|
|
50
|
+
|
|
51
|
+
export const usersQueryOptions = (cursor?: string) =>
|
|
52
|
+
queryOptions({
|
|
53
|
+
queryKey: ['users', { cursor }],
|
|
54
|
+
queryFn: () =>
|
|
55
|
+
fetchValidated(
|
|
56
|
+
`/api/users${cursor ? `?cursor=${cursor}` : ''}`,
|
|
57
|
+
UsersResponseSchema
|
|
58
|
+
),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const userQueryOptions = (id: string) =>
|
|
62
|
+
queryOptions({
|
|
63
|
+
queryKey: ['users', id],
|
|
64
|
+
queryFn: () => fetchValidated(`/api/users/${id}`, UserSchema),
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Create Server Function (Web)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// src/routes/api/users.ts
|
|
72
|
+
import { createServerFn } from '@tanstack/start';
|
|
73
|
+
import { z } from 'zod';
|
|
74
|
+
import { db } from '@/lib/db';
|
|
75
|
+
import { users } from '@/lib/schema';
|
|
76
|
+
|
|
77
|
+
export const getUsers = createServerFn('GET', async () => {
|
|
78
|
+
return await db.select().from(users);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const CreateUserSchema = z.object({
|
|
82
|
+
email: z.string().email(),
|
|
83
|
+
name: z.string().min(1),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const createUser = createServerFn('POST', async (input: unknown) => {
|
|
87
|
+
const data = CreateUserSchema.parse(input);
|
|
88
|
+
const [user] = await db.insert(users).values(data).returning();
|
|
89
|
+
return user;
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 4. Use in Component
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// Web
|
|
97
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
98
|
+
import { usersQueryOptions } from '@/lib/queries';
|
|
99
|
+
import { createUser } from '@/routes/api/users';
|
|
100
|
+
|
|
101
|
+
function UsersPage() {
|
|
102
|
+
const { data, isLoading } = useQuery(usersQueryOptions());
|
|
103
|
+
const queryClient = useQueryClient();
|
|
104
|
+
|
|
105
|
+
const mutation = useMutation({
|
|
106
|
+
mutationFn: createUser,
|
|
107
|
+
onSuccess: () => {
|
|
108
|
+
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (isLoading) return <div>Loading...</div>;
|
|
113
|
+
return <UserList users={data.users} />;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// Mobile
|
|
119
|
+
import { useQuery } from '@tanstack/react-query';
|
|
120
|
+
import { usersQueryOptions } from '@/api/queries';
|
|
121
|
+
|
|
122
|
+
function UsersScreen() {
|
|
123
|
+
const { data, isLoading } = useQuery(usersQueryOptions());
|
|
124
|
+
|
|
125
|
+
if (isLoading) return <LoadingSpinner />;
|
|
126
|
+
return <UserList users={data.users} />;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Conventions
|
|
131
|
+
|
|
132
|
+
- Schema names end with `Schema`
|
|
133
|
+
- Types derived from schemas with `z.infer`
|
|
134
|
+
- Query options follow pattern: `[resource]QueryOptions`
|
|
135
|
+
- Always validate responses with `fetchValidated`
|
|
136
|
+
- Use cursor-based pagination for lists
|
|
137
|
+
- Include `total` count for paginated responses
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-component
|
|
3
|
+
description: Create a new React component following project conventions
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add Component
|
|
7
|
+
|
|
8
|
+
Creates a new React component with proper structure and patterns.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/add-component Button
|
|
14
|
+
/add-component UserProfile --web
|
|
15
|
+
/add-component ArticleCard --mobile
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Process
|
|
19
|
+
|
|
20
|
+
1. **Determine location**:
|
|
21
|
+
- `--web`: `apps/web/src/components/`
|
|
22
|
+
- `--mobile`: `apps/mobile/src/components/`
|
|
23
|
+
- `--ui`: `packages/ui/src/components/`
|
|
24
|
+
- Default: Ask user
|
|
25
|
+
|
|
26
|
+
2. **Create component file**:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// components/[name].tsx
|
|
30
|
+
import type { ComponentProps } from 'react';
|
|
31
|
+
|
|
32
|
+
interface [Name]Props {
|
|
33
|
+
// Props here
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function [Name]({ ...props }: [Name]Props) {
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
{/* Component content */}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. **Add to exports** (if packages/ui):
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
// components/index.ts
|
|
49
|
+
export { [Name] } from './[name]';
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
4. **For mobile components**, use React Native primitives:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
56
|
+
|
|
57
|
+
export function [Name]() {
|
|
58
|
+
return (
|
|
59
|
+
<View style={styles.container}>
|
|
60
|
+
<Text>Content</Text>
|
|
61
|
+
</View>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const styles = StyleSheet.create({
|
|
66
|
+
container: {
|
|
67
|
+
// styles
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Conventions
|
|
73
|
+
|
|
74
|
+
- PascalCase component names
|
|
75
|
+
- kebab-case file names
|
|
76
|
+
- Props interface named `[Component]Props`
|
|
77
|
+
- Functional components only (no classes)
|
|
78
|
+
- Use `cn()` for conditional Tailwind classes (web)
|
|
79
|
+
- Use StyleSheet for styles (mobile)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-route
|
|
3
|
+
description: Add a new route to the web or mobile app
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add Route
|
|
7
|
+
|
|
8
|
+
Creates a new route with proper file-based routing structure.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/add-route dashboard
|
|
14
|
+
/add-route settings --web
|
|
15
|
+
/add-route profile --mobile
|
|
16
|
+
/add-route "users/[id]" --web # Dynamic route
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Web Routes (TanStack Start)
|
|
20
|
+
|
|
21
|
+
Location: `apps/web/src/routes/`
|
|
22
|
+
|
|
23
|
+
### Static Route
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// routes/dashboard.tsx
|
|
27
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
28
|
+
|
|
29
|
+
export const Route = createFileRoute('/dashboard')({
|
|
30
|
+
component: DashboardPage,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function DashboardPage() {
|
|
34
|
+
return (
|
|
35
|
+
<main className="container py-8">
|
|
36
|
+
<h1 className="text-3xl font-bold">Dashboard</h1>
|
|
37
|
+
</main>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Dynamic Route
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// routes/users/$id.tsx
|
|
46
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
47
|
+
|
|
48
|
+
export const Route = createFileRoute('/users/$id')({
|
|
49
|
+
component: UserPage,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function UserPage() {
|
|
53
|
+
const { id } = Route.useParams();
|
|
54
|
+
return <div>User {id}</div>;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### With Loader
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// routes/projects.tsx
|
|
62
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
63
|
+
import { useSuspenseQuery } from '@tanstack/react-query';
|
|
64
|
+
import { projectsQueryOptions } from '@/lib/queries';
|
|
65
|
+
|
|
66
|
+
export const Route = createFileRoute('/projects')({
|
|
67
|
+
loader: ({ context }) =>
|
|
68
|
+
context.queryClient.ensureQueryData(projectsQueryOptions),
|
|
69
|
+
component: ProjectsPage,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
function ProjectsPage() {
|
|
73
|
+
const { data } = useSuspenseQuery(projectsQueryOptions);
|
|
74
|
+
return <ProjectList projects={data} />;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Mobile Routes (Expo Router)
|
|
79
|
+
|
|
80
|
+
Location: `apps/mobile/app/`
|
|
81
|
+
|
|
82
|
+
### Static Route
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
// app/settings.tsx
|
|
86
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
87
|
+
|
|
88
|
+
export default function SettingsScreen() {
|
|
89
|
+
return (
|
|
90
|
+
<View style={styles.container}>
|
|
91
|
+
<Text style={styles.title}>Settings</Text>
|
|
92
|
+
</View>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const styles = StyleSheet.create({
|
|
97
|
+
container: { flex: 1, padding: 16 },
|
|
98
|
+
title: { fontSize: 24, fontWeight: 'bold' },
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Dynamic Route
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// app/article/[slug].tsx
|
|
106
|
+
import { useLocalSearchParams } from 'expo-router';
|
|
107
|
+
import { View, Text } from 'react-native';
|
|
108
|
+
|
|
109
|
+
export default function ArticleScreen() {
|
|
110
|
+
const { slug } = useLocalSearchParams<{ slug: string }>();
|
|
111
|
+
return (
|
|
112
|
+
<View>
|
|
113
|
+
<Text>Article: {slug}</Text>
|
|
114
|
+
</View>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Tab Route
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
// app/(tabs)/home.tsx
|
|
123
|
+
export default function HomeTab() {
|
|
124
|
+
return <HomeScreen />;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Conventions
|
|
129
|
+
|
|
130
|
+
- File name = route path
|
|
131
|
+
- `$param` for TanStack Router dynamic segments
|
|
132
|
+
- `[param]` for Expo Router dynamic segments
|
|
133
|
+
- `(group)` folders for layout groups (don't affect URL)
|
|
134
|
+
- `_layout.tsx` for nested layouts
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-store
|
|
3
|
+
description: Create a new Zustand store with persistence (mobile)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add Store
|
|
7
|
+
|
|
8
|
+
Creates a new Zustand store with AsyncStorage persistence for the mobile app.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/add-store notifications
|
|
14
|
+
/add-store cart
|
|
15
|
+
/add-store preferences
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Process
|
|
19
|
+
|
|
20
|
+
### 1. Create Store
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// src/lib/store.ts (add to existing file)
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// NOTIFICATIONS STORE
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
interface NotificationsState {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
sound: boolean;
|
|
32
|
+
vibration: boolean;
|
|
33
|
+
setEnabled: (enabled: boolean) => void;
|
|
34
|
+
setSound: (sound: boolean) => void;
|
|
35
|
+
setVibration: (vibration: boolean) => void;
|
|
36
|
+
reset: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const notificationsInitialState = {
|
|
40
|
+
enabled: true,
|
|
41
|
+
sound: true,
|
|
42
|
+
vibration: true,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const useNotificationsStore = create<NotificationsState>()(
|
|
46
|
+
persist(
|
|
47
|
+
(set) => ({
|
|
48
|
+
...notificationsInitialState,
|
|
49
|
+
setEnabled: (enabled) => set({ enabled }),
|
|
50
|
+
setSound: (sound) => set({ sound }),
|
|
51
|
+
setVibration: (vibration) => set({ vibration }),
|
|
52
|
+
reset: () => set(notificationsInitialState),
|
|
53
|
+
}),
|
|
54
|
+
{
|
|
55
|
+
name: 'notifications-storage',
|
|
56
|
+
storage: createJSONStorage(() => AsyncStorage),
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Use with Selectors
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// Always use selectors to prevent unnecessary re-renders
|
|
66
|
+
function NotificationToggle() {
|
|
67
|
+
// Good - only re-renders when 'enabled' changes
|
|
68
|
+
const enabled = useNotificationsStore((state) => state.enabled);
|
|
69
|
+
const setEnabled = useNotificationsStore((state) => state.setEnabled);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Switch value={enabled} onValueChange={setEnabled} />
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Multiple Selectors
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// For multiple values, use shallow comparison
|
|
81
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
82
|
+
|
|
83
|
+
function NotificationSettings() {
|
|
84
|
+
const { sound, vibration, setSound, setVibration } = useNotificationsStore(
|
|
85
|
+
useShallow((state) => ({
|
|
86
|
+
sound: state.sound,
|
|
87
|
+
vibration: state.vibration,
|
|
88
|
+
setSound: state.setSound,
|
|
89
|
+
setVibration: state.setVibration,
|
|
90
|
+
}))
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<View>
|
|
95
|
+
<Switch value={sound} onValueChange={setSound} />
|
|
96
|
+
<Switch value={vibration} onValueChange={setVibration} />
|
|
97
|
+
</View>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Store Patterns
|
|
103
|
+
|
|
104
|
+
### Computed Values
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
interface CartState {
|
|
108
|
+
items: CartItem[];
|
|
109
|
+
// Computed via selector, not stored
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Use selector for computed values
|
|
113
|
+
const totalPrice = useCartStore((state) =>
|
|
114
|
+
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Async Actions
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
interface UserState {
|
|
122
|
+
user: User | null;
|
|
123
|
+
loading: boolean;
|
|
124
|
+
fetchUser: (id: string) => Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const useUserStore = create<UserState>()(
|
|
128
|
+
persist(
|
|
129
|
+
(set) => ({
|
|
130
|
+
user: null,
|
|
131
|
+
loading: false,
|
|
132
|
+
fetchUser: async (id) => {
|
|
133
|
+
set({ loading: true });
|
|
134
|
+
try {
|
|
135
|
+
const user = await fetchUser(id);
|
|
136
|
+
set({ user, loading: false });
|
|
137
|
+
} catch {
|
|
138
|
+
set({ loading: false });
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
{
|
|
143
|
+
name: 'user-storage',
|
|
144
|
+
storage: createJSONStorage(() => AsyncStorage),
|
|
145
|
+
partialize: (state) => ({ user: state.user }), // Only persist user, not loading
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Conventions
|
|
152
|
+
|
|
153
|
+
- One store per domain (app, search, notifications, etc.)
|
|
154
|
+
- Separate concerns - don't put everything in one store
|
|
155
|
+
- Always use selectors
|
|
156
|
+
- Use `partialize` to exclude transient state from persistence
|
|
157
|
+
- Include reset function for clearing state
|
|
158
|
+
- Name storage keys descriptively: `[domain]-storage`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bunx lint-staged
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
22
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guide for using AI agents effectively in this codebase.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Task | Agent Type | Skill |
|
|
8
|
+
|------|------------|-------|
|
|
9
|
+
| Explore codebase | `Explore` | - |
|
|
10
|
+
| Plan implementation | `Plan` | `superpowers:writing-plans` |
|
|
11
|
+
| Execute plan | `general-purpose` | `superpowers:executing-plans` |
|
|
12
|
+
| Debug issues | `general-purpose` | `superpowers:systematic-debugging` |
|
|
13
|
+
| Code review | `code-reviewer` | `superpowers:requesting-code-review` |
|
|
14
|
+
| Create component | `general-purpose` | Project: `add-component` |
|
|
15
|
+
| Add route | `general-purpose` | Project: `add-route` |
|
|
16
|
+
| Add API endpoint | `general-purpose` | Project: `add-api-endpoint` |
|
|
17
|
+
|
|
18
|
+
## Workflows
|
|
19
|
+
|
|
20
|
+
### Feature Development
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
1. Brainstorm → superpowers:brainstorming
|
|
24
|
+
2. Write Plan → superpowers:writing-plans
|
|
25
|
+
3. Execute → superpowers:subagent-driven-development
|
|
26
|
+
4. Review → superpowers:requesting-code-review
|
|
27
|
+
5. Finish → superpowers:finishing-a-development-branch
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Bug Fixing
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
1. Debug → superpowers:systematic-debugging
|
|
34
|
+
2. Fix → TDD approach (test first)
|
|
35
|
+
3. Verify → superpowers:verification-before-completion
|
|
36
|
+
4. Review → superpowers:requesting-code-review
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Exploration
|
|
40
|
+
|
|
41
|
+
Use the `Explore` agent for:
|
|
42
|
+
- Understanding unfamiliar parts of the codebase
|
|
43
|
+
- Finding where functionality is implemented
|
|
44
|
+
- Mapping dependencies between modules
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Task tool with subagent_type=Explore:
|
|
48
|
+
"How does authentication work in this app?"
|
|
49
|
+
"Where are API routes defined?"
|
|
50
|
+
"What components use the useAppStore hook?"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Project Skills
|
|
54
|
+
|
|
55
|
+
Custom skills in `.claude/skills/`:
|
|
56
|
+
|
|
57
|
+
### `add-component`
|
|
58
|
+
|
|
59
|
+
Creates a new React component with proper structure:
|
|
60
|
+
- Component file with TypeScript
|
|
61
|
+
- Exports from index
|
|
62
|
+
- Follows project patterns
|
|
63
|
+
|
|
64
|
+
### `add-route`
|
|
65
|
+
|
|
66
|
+
Adds a new route to the web or mobile app:
|
|
67
|
+
- Creates route file with proper exports
|
|
68
|
+
- Sets up loader if needed
|
|
69
|
+
- Follows file-based routing conventions
|
|
70
|
+
|
|
71
|
+
### `add-api-endpoint`
|
|
72
|
+
|
|
73
|
+
Creates a new API endpoint:
|
|
74
|
+
- Zod schema for request/response
|
|
75
|
+
- Query options factory
|
|
76
|
+
- fetchValidated integration
|
|
77
|
+
|
|
78
|
+
## Best Practices
|
|
79
|
+
|
|
80
|
+
### Do
|
|
81
|
+
|
|
82
|
+
- **Use skills** when they exist for the task
|
|
83
|
+
- **Use Explore agent** for codebase questions
|
|
84
|
+
- **Plan first** for multi-file changes
|
|
85
|
+
- **TDD** for all new functionality
|
|
86
|
+
- **Small commits** after each logical change
|
|
87
|
+
- **Run checks** before claiming completion
|
|
88
|
+
|
|
89
|
+
### Don't
|
|
90
|
+
|
|
91
|
+
- Don't skip planning for complex features
|
|
92
|
+
- Don't write code without reading existing patterns
|
|
93
|
+
- Don't commit without running `bun check`
|
|
94
|
+
- Don't claim "done" without verification
|
|
95
|
+
|
|
96
|
+
## Parallel Agents
|
|
97
|
+
|
|
98
|
+
For independent tasks, dispatch multiple agents in parallel:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Good candidates for parallelization:
|
|
102
|
+
- Independent component creation
|
|
103
|
+
- Separate test files
|
|
104
|
+
- Unrelated bug fixes
|
|
105
|
+
- Documentation updates
|
|
106
|
+
|
|
107
|
+
Not parallelizable:
|
|
108
|
+
- Sequential dependencies
|
|
109
|
+
- Shared file modifications
|
|
110
|
+
- Database migrations
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Agent Communication
|
|
114
|
+
|
|
115
|
+
When working with multiple agents:
|
|
116
|
+
|
|
117
|
+
1. **Context handoff**: Provide full task description, don't reference "above"
|
|
118
|
+
2. **Clear boundaries**: Each agent owns specific files
|
|
119
|
+
3. **Verification**: Each agent verifies their own work
|
|
120
|
+
4. **Review**: Use code-reviewer agent after implementation
|
|
121
|
+
|
|
122
|
+
## Customization
|
|
123
|
+
|
|
124
|
+
### Adding New Skills
|
|
125
|
+
|
|
126
|
+
1. Create skill file in `.claude/skills/`
|
|
127
|
+
2. Follow the skill template format
|
|
128
|
+
3. Document in this file
|
|
129
|
+
|
|
130
|
+
### Adjusting Rules
|
|
131
|
+
|
|
132
|
+
Edit `.claude/settings.json` to:
|
|
133
|
+
- Add custom instructions
|
|
134
|
+
- Configure tool permissions
|
|
135
|
+
- Set project-specific behaviors
|