@kood/claude-code 0.2.0 → 0.2.2

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 (28) hide show
  1. package/dist/index.js +62 -18
  2. package/package.json +1 -1
  3. package/templates/.claude/agents/code-reviewer.md +31 -0
  4. package/templates/.claude/agents/debug-detective.md +37 -0
  5. package/templates/.claude/agents/refactor-advisor.md +44 -0
  6. package/templates/.claude/agents/test-writer.md +41 -0
  7. package/templates/.claude/skills/frontend-design/SKILL.md +310 -0
  8. package/templates/.claude/skills/frontend-design/references/animation-patterns.md +446 -0
  9. package/templates/.claude/skills/frontend-design/references/colors-2026.md +244 -0
  10. package/templates/.claude/skills/frontend-design/references/typography-2026.md +302 -0
  11. package/templates/.claude/skills/gemini-review/SKILL.md +1 -1
  12. package/templates/hono/docs/library/drizzle/cloudflare-d1.md +247 -0
  13. package/templates/hono/docs/library/drizzle/config.md +167 -0
  14. package/templates/hono/docs/library/drizzle/index.md +259 -0
  15. package/templates/tanstack-start/docs/library/drizzle/cloudflare-d1.md +146 -0
  16. package/templates/tanstack-start/docs/library/drizzle/config.md +118 -0
  17. package/templates/tanstack-start/docs/library/drizzle/crud.md +205 -0
  18. package/templates/tanstack-start/docs/library/drizzle/index.md +79 -0
  19. package/templates/tanstack-start/docs/library/drizzle/relations.md +202 -0
  20. package/templates/tanstack-start/docs/library/drizzle/schema.md +154 -0
  21. package/templates/tanstack-start/docs/library/drizzle/setup.md +95 -0
  22. package/templates/tanstack-start/docs/library/drizzle/transactions.md +127 -0
  23. package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +204 -0
  24. package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +195 -0
  25. package/templates/tanstack-start/docs/library/tanstack-router/index.md +150 -0
  26. package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +150 -0
  27. package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +203 -0
  28. package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +213 -0
@@ -0,0 +1,203 @@
1
+ # TanStack Router - Route Context
2
+
3
+ beforeLoad, context, protected routes 구현.
4
+
5
+ ## beforeLoad
6
+
7
+ 라우트 로드 전 실행. 인증 체크, 리다이렉트, context 추가.
8
+
9
+ ```tsx
10
+ import { createFileRoute, redirect } from '@tanstack/react-router'
11
+
12
+ export const Route = createFileRoute('/dashboard')({
13
+ beforeLoad: async ({ context, location }) => {
14
+ // context에서 인증 상태 확인
15
+ if (!context.auth.isAuthenticated) {
16
+ throw redirect({
17
+ to: '/login',
18
+ search: { redirect: location.href },
19
+ })
20
+ }
21
+
22
+ // 추가 context 반환 (loader에서 사용 가능)
23
+ return {
24
+ userPermissions: await fetchPermissions(context.auth.user.id),
25
+ }
26
+ },
27
+ loader: async ({ context }) => {
28
+ // beforeLoad에서 반환한 context 사용
29
+ return fetchDashboardData(context.userPermissions)
30
+ },
31
+ component: DashboardPage,
32
+ })
33
+ ```
34
+
35
+ ## Protected Routes (Layout)
36
+
37
+ pathless layout으로 보호된 라우트 그룹 생성.
38
+
39
+ ### _authed.tsx (Layout)
40
+
41
+ ```tsx
42
+ // routes/_authed.tsx
43
+ import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
44
+ import { getCurrentUser } from '@/functions/auth'
45
+
46
+ export const Route = createFileRoute('/_authed')({
47
+ beforeLoad: async ({ location }) => {
48
+ const user = await getCurrentUser()
49
+
50
+ if (!user) {
51
+ throw redirect({
52
+ to: '/login',
53
+ search: { redirect: location.href },
54
+ })
55
+ }
56
+
57
+ // 하위 라우트에서 사용할 context
58
+ return { user }
59
+ },
60
+ component: () => <Outlet />,
61
+ })
62
+ ```
63
+
64
+ ### _authed/dashboard.tsx
65
+
66
+ ```tsx
67
+ // routes/_authed/dashboard.tsx
68
+ import { createFileRoute } from '@tanstack/react-router'
69
+
70
+ export const Route = createFileRoute('/_authed/dashboard')({
71
+ component: DashboardPage,
72
+ })
73
+
74
+ function DashboardPage() {
75
+ // _authed에서 전달된 context
76
+ const { user } = Route.useRouteContext()
77
+
78
+ return (
79
+ <div>
80
+ <h1>Welcome, {user.name}!</h1>
81
+ </div>
82
+ )
83
+ }
84
+ ```
85
+
86
+ ### 구조
87
+
88
+ ```
89
+ routes/
90
+ ├── _authed.tsx # Protected layout (beforeLoad에서 인증 체크)
91
+ ├── _authed/
92
+ │ ├── dashboard.tsx # /dashboard (protected)
93
+ │ ├── settings.tsx # /settings (protected)
94
+ │ └── profile.tsx # /profile (protected)
95
+ ├── login.tsx # /login (public)
96
+ └── index.tsx # / (public)
97
+ ```
98
+
99
+ ## Root Context
100
+
101
+ 전역 context 설정 (QueryClient, Auth 등).
102
+
103
+ ### __root.tsx
104
+
105
+ ```tsx
106
+ // routes/__root.tsx
107
+ import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
108
+ import type { QueryClient } from '@tanstack/react-query'
109
+
110
+ interface RouterContext {
111
+ queryClient: QueryClient
112
+ auth: {
113
+ isAuthenticated: boolean
114
+ user: { id: string; name: string } | null
115
+ }
116
+ }
117
+
118
+ export const Route = createRootRouteWithContext<RouterContext>()({
119
+ component: RootLayout,
120
+ })
121
+
122
+ function RootLayout() {
123
+ return (
124
+ <div>
125
+ <Outlet />
126
+ </div>
127
+ )
128
+ }
129
+ ```
130
+
131
+ ### Router 생성
132
+
133
+ ```tsx
134
+ // app/router.tsx
135
+ import { createRouter } from '@tanstack/react-router'
136
+ import { routeTree } from './routeTree.gen'
137
+ import { queryClient } from './query-client'
138
+
139
+ export const router = createRouter({
140
+ routeTree,
141
+ context: {
142
+ queryClient,
143
+ auth: {
144
+ isAuthenticated: false,
145
+ user: null,
146
+ },
147
+ },
148
+ })
149
+ ```
150
+
151
+ ## redirect
152
+
153
+ ```tsx
154
+ import { redirect } from '@tanstack/react-router'
155
+
156
+ // 기본 리다이렉트
157
+ throw redirect({ to: '/login' })
158
+
159
+ // Search params 포함
160
+ throw redirect({
161
+ to: '/login',
162
+ search: { redirect: '/dashboard' },
163
+ })
164
+
165
+ // 동적 파라미터
166
+ throw redirect({
167
+ to: '/posts/$postId',
168
+ params: { postId: '123' },
169
+ })
170
+
171
+ // replace (뒤로가기 X)
172
+ throw redirect({
173
+ to: '/home',
174
+ replace: true,
175
+ })
176
+ ```
177
+
178
+ ## Context 사용 위치
179
+
180
+ | 위치 | 접근 방법 |
181
+ |------|----------|
182
+ | beforeLoad | `{ context }` 파라미터 |
183
+ | loader | `{ context }` 파라미터 |
184
+ | component | `Route.useRouteContext()` |
185
+
186
+ ```tsx
187
+ export const Route = createFileRoute('/example')({
188
+ beforeLoad: ({ context }) => {
189
+ console.log(context.auth)
190
+ return { extra: 'data' }
191
+ },
192
+ loader: ({ context }) => {
193
+ // beforeLoad 반환값도 포함됨
194
+ console.log(context.extra)
195
+ },
196
+ component: ExamplePage,
197
+ })
198
+
199
+ function ExamplePage() {
200
+ const context = Route.useRouteContext()
201
+ // context.auth, context.extra 모두 접근 가능
202
+ }
203
+ ```
@@ -0,0 +1,213 @@
1
+ # TanStack Router - Search Params
2
+
3
+ Type-safe URL search params with Zod validation.
4
+
5
+ ## 기본 사용
6
+
7
+ ```tsx
8
+ import { createFileRoute } from '@tanstack/react-router'
9
+ import { z } from 'zod'
10
+
11
+ // 스키마 정의
12
+ const productSearchSchema = z.object({
13
+ page: z.number().catch(1),
14
+ filter: z.string().catch(''),
15
+ sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
16
+ })
17
+
18
+ type ProductSearch = z.infer<typeof productSearchSchema>
19
+
20
+ // 라우트에 적용
21
+ export const Route = createFileRoute('/products')({
22
+ validateSearch: productSearchSchema,
23
+ component: ProductsPage,
24
+ })
25
+
26
+ // 컴포넌트에서 사용
27
+ function ProductsPage() {
28
+ const { page, filter, sort } = Route.useSearch()
29
+
30
+ return (
31
+ <div>
32
+ <p>Page: {page}</p>
33
+ <p>Filter: {filter}</p>
34
+ <p>Sort: {sort}</p>
35
+ </div>
36
+ )
37
+ }
38
+ ```
39
+
40
+ ## Zod 스키마 패턴
41
+
42
+ ```tsx
43
+ import { z } from 'zod'
44
+
45
+ // 기본값 (.catch)
46
+ const schema = z.object({
47
+ page: z.number().catch(1), // 파싱 실패 시 1
48
+ search: z.string().optional(), // undefined 허용
49
+ tab: z.enum(['all', 'active']).catch('all'),
50
+ })
51
+
52
+ // 복잡한 타입
53
+ const advancedSchema = z.object({
54
+ // 배열
55
+ tags: z.array(z.string()).catch([]),
56
+
57
+ // 날짜
58
+ from: z.string().date().optional(),
59
+ to: z.string().date().optional(),
60
+
61
+ // 숫자 범위
62
+ minPrice: z.number().min(0).catch(0),
63
+ maxPrice: z.number().max(10000).catch(10000),
64
+
65
+ // Boolean
66
+ inStock: z.boolean().catch(true),
67
+ })
68
+ ```
69
+
70
+ ## Search Params 업데이트
71
+
72
+ ### Link로 업데이트
73
+
74
+ ```tsx
75
+ import { Link } from '@tanstack/react-router'
76
+
77
+ // 전체 교체
78
+ <Link to="/products" search={{ page: 1, sort: 'newest' }}>
79
+ Reset
80
+ </Link>
81
+
82
+ // 병합
83
+ <Link to="/products" search={prev => ({ ...prev, page: 2 })}>
84
+ Next Page
85
+ </Link>
86
+
87
+ // 특정 값만 변경
88
+ <Link
89
+ to="/products"
90
+ search={prev => ({ ...prev, sort: 'price' })}
91
+ >
92
+ Sort by Price
93
+ </Link>
94
+ ```
95
+
96
+ ### useNavigate로 업데이트
97
+
98
+ ```tsx
99
+ import { useNavigate } from '@tanstack/react-router'
100
+
101
+ function Pagination() {
102
+ const navigate = useNavigate()
103
+ const { page } = Route.useSearch()
104
+
105
+ const goToPage = (newPage: number) => {
106
+ navigate({
107
+ to: '/products',
108
+ search: prev => ({ ...prev, page: newPage }),
109
+ })
110
+ }
111
+
112
+ return (
113
+ <div>
114
+ <button onClick={() => goToPage(page - 1)} disabled={page <= 1}>
115
+ Prev
116
+ </button>
117
+ <span>Page {page}</span>
118
+ <button onClick={() => goToPage(page + 1)}>
119
+ Next
120
+ </button>
121
+ </div>
122
+ )
123
+ }
124
+ ```
125
+
126
+ ## 실전 예시
127
+
128
+ ### 필터 + 정렬 + 페이지네이션
129
+
130
+ ```tsx
131
+ import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'
132
+ import { z } from 'zod'
133
+
134
+ const searchSchema = z.object({
135
+ page: z.number().min(1).catch(1),
136
+ pageSize: z.number().catch(10),
137
+ search: z.string().catch(''),
138
+ category: z.enum(['all', 'tech', 'lifestyle']).catch('all'),
139
+ sort: z.enum(['newest', 'oldest', 'popular']).catch('newest'),
140
+ })
141
+
142
+ export const Route = createFileRoute('/posts')({
143
+ validateSearch: searchSchema,
144
+ loaderDeps: ({ search }) => ({ search }), // search 변경 시 loader 재실행
145
+ loader: async ({ deps: { search } }) => {
146
+ return fetchPosts(search)
147
+ },
148
+ component: PostsPage,
149
+ })
150
+
151
+ function PostsPage() {
152
+ const { page, search, category, sort } = Route.useSearch()
153
+ const posts = Route.useLoaderData()
154
+ const navigate = useNavigate()
155
+
156
+ const updateSearch = (updates: Partial<typeof searchSchema._type>) => {
157
+ navigate({
158
+ to: '/posts',
159
+ search: prev => ({ ...prev, ...updates, page: 1 }), // 필터 변경 시 1페이지로
160
+ })
161
+ }
162
+
163
+ return (
164
+ <div>
165
+ {/* 검색 */}
166
+ <input
167
+ value={search}
168
+ onChange={e => updateSearch({ search: e.target.value })}
169
+ placeholder="Search..."
170
+ />
171
+
172
+ {/* 카테고리 필터 */}
173
+ <select
174
+ value={category}
175
+ onChange={e => updateSearch({ category: e.target.value as any })}
176
+ >
177
+ <option value="all">All</option>
178
+ <option value="tech">Tech</option>
179
+ <option value="lifestyle">Lifestyle</option>
180
+ </select>
181
+
182
+ {/* 정렬 */}
183
+ <select
184
+ value={sort}
185
+ onChange={e => updateSearch({ sort: e.target.value as any })}
186
+ >
187
+ <option value="newest">Newest</option>
188
+ <option value="oldest">Oldest</option>
189
+ <option value="popular">Popular</option>
190
+ </select>
191
+
192
+ {/* 목록 */}
193
+ {posts.map(post => (
194
+ <div key={post.id}>{post.title}</div>
195
+ ))}
196
+ </div>
197
+ )
198
+ }
199
+ ```
200
+
201
+ ## loaderDeps
202
+
203
+ Search params 변경 시 loader 재실행하려면 `loaderDeps` 필요.
204
+
205
+ ```tsx
206
+ export const Route = createFileRoute('/products')({
207
+ validateSearch: searchSchema,
208
+ loaderDeps: ({ search }) => ({ search }), // 의존성 선언
209
+ loader: async ({ deps: { search } }) => {
210
+ return fetchProducts(search)
211
+ },
212
+ })
213
+ ```