@smicolon/ai-kit 0.1.0 → 0.2.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-plugin/CLAUDE.md +7 -0
- package/.claude-plugin/marketplace.json +373 -0
- package/README.md +26 -16
- package/dist/index.js +146 -38
- package/package.json +4 -3
- package/packs/architect/CHANGELOG.md +17 -0
- package/packs/architect/README.md +58 -0
- package/packs/architect/agents/system-architect.md +768 -0
- package/packs/architect/commands/diagram-create.md +300 -0
- package/packs/better-auth/.claude-plugin/plugin.json +14 -0
- package/packs/better-auth/.mcp.json +14 -0
- package/packs/better-auth/CHANGELOG.md +26 -0
- package/packs/better-auth/README.md +125 -0
- package/packs/better-auth/agents/auth-architect.md +278 -0
- package/packs/better-auth/commands/auth-provider-add.md +265 -0
- package/packs/better-auth/commands/auth-setup.md +298 -0
- package/packs/better-auth/skills/auth-security/SKILL.md +425 -0
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +455 -0
- package/packs/dev-loop/.claude-plugin/plugin.json +10 -0
- package/packs/dev-loop/CHANGELOG.md +69 -0
- package/packs/dev-loop/README.md +155 -0
- package/packs/dev-loop/commands/cancel-dev.md +21 -0
- package/packs/dev-loop/commands/dev-loop.md +72 -0
- package/packs/dev-loop/commands/dev-plan.md +351 -0
- package/packs/dev-loop/hooks/hooks.json +15 -0
- package/packs/dev-loop/hooks/stop-hook.sh +178 -0
- package/packs/dev-loop/scripts/setup-dev-loop.sh +194 -0
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +249 -0
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +874 -0
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +260 -0
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +275 -0
- package/packs/django/CHANGELOG.md +39 -0
- package/packs/django/README.md +92 -0
- package/packs/django/agents/django-architect.md +182 -0
- package/packs/django/agents/django-builder.md +250 -0
- package/packs/django/agents/django-feature-based.md +420 -0
- package/packs/django/agents/django-reviewer.md +253 -0
- package/packs/django/agents/django-tester.md +230 -0
- package/packs/django/commands/api-endpoint.md +285 -0
- package/packs/django/commands/model-create.md +178 -0
- package/packs/django/commands/test-generate.md +325 -0
- package/packs/django/rules/migrations.md +138 -0
- package/packs/django/rules/models.md +167 -0
- package/packs/django/rules/serializers.md +126 -0
- package/packs/django/rules/services.md +131 -0
- package/packs/django/rules/tests.md +140 -0
- package/packs/django/rules/views.md +102 -0
- package/packs/django/skills/import-convention-enforcer/SKILL.md +226 -0
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +343 -0
- package/packs/django/skills/migration-safety-checker/SKILL.md +375 -0
- package/packs/django/skills/model-entity-validator/SKILL.md +298 -0
- package/packs/django/skills/performance-optimizer/SKILL.md +447 -0
- package/packs/django/skills/red-phase-verifier/SKILL.md +180 -0
- package/packs/django/skills/security-first-validator/SKILL.md +435 -0
- package/packs/django/skills/test-coverage-advisor/SKILL.md +394 -0
- package/packs/django/skills/test-validity-checker/SKILL.md +194 -0
- package/packs/failure-log/.claude-plugin/plugin.json +14 -0
- package/packs/failure-log/CHANGELOG.md +20 -0
- package/packs/failure-log/README.md +168 -0
- package/packs/failure-log/commands/failure-add.md +106 -0
- package/packs/failure-log/commands/failure-list.md +89 -0
- package/packs/failure-log/hooks/hooks.json +16 -0
- package/packs/failure-log/hooks/scripts/inject-failures.sh +64 -0
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +164 -0
- package/packs/flutter/.claude-plugin/plugin.json +10 -0
- package/packs/flutter/CHANGELOG.md +19 -0
- package/packs/flutter/README.md +170 -0
- package/packs/flutter/agents/flutter-architect.md +166 -0
- package/packs/flutter/agents/flutter-builder.md +303 -0
- package/packs/flutter/agents/release-manager.md +355 -0
- package/packs/flutter/commands/fastlane-setup.md +188 -0
- package/packs/flutter/commands/flutter-build.md +90 -0
- package/packs/flutter/commands/flutter-deploy.md +133 -0
- package/packs/flutter/commands/flutter-test.md +117 -0
- package/packs/flutter/commands/signing-setup.md +209 -0
- package/packs/flutter/hooks/hooks.json +17 -0
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +193 -0
- package/packs/flutter/skills/flutter-architecture/SKILL.md +127 -0
- package/packs/flutter/skills/store-publishing/SKILL.md +163 -0
- package/packs/hono/.claude-plugin/plugin.json +19 -0
- package/packs/hono/CHANGELOG.md +19 -0
- package/packs/hono/README.md +143 -0
- package/packs/hono/agents/hono-architect.md +240 -0
- package/packs/hono/agents/hono-builder.md +285 -0
- package/packs/hono/agents/hono-reviewer.md +279 -0
- package/packs/hono/agents/hono-tester.md +346 -0
- package/packs/hono/commands/middleware-create.md +223 -0
- package/packs/hono/commands/project-init.md +306 -0
- package/packs/hono/commands/route-create.md +153 -0
- package/packs/hono/commands/rpc-client.md +263 -0
- package/packs/hono/hooks/hooks.json +4 -0
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +408 -0
- package/packs/hono/skills/hono-patterns/SKILL.md +309 -0
- package/packs/hono/skills/rpc-typesafe/SKILL.md +388 -0
- package/packs/hono/skills/zod-validation/SKILL.md +332 -0
- package/packs/nestjs/CHANGELOG.md +29 -0
- package/packs/nestjs/README.md +75 -0
- package/packs/nestjs/agents/nestjs-architect.md +402 -0
- package/packs/nestjs/agents/nestjs-builder.md +301 -0
- package/packs/nestjs/agents/nestjs-tester.md +437 -0
- package/packs/nestjs/commands/module-create.md +369 -0
- package/packs/nestjs/rules/controllers.md +92 -0
- package/packs/nestjs/rules/dto.md +124 -0
- package/packs/nestjs/rules/entities.md +102 -0
- package/packs/nestjs/rules/services.md +106 -0
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +389 -0
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +365 -0
- package/packs/nextjs/CHANGELOG.md +36 -0
- package/packs/nextjs/README.md +76 -0
- package/packs/nextjs/agents/frontend-tester.md +680 -0
- package/packs/nextjs/agents/frontend-visual.md +820 -0
- package/packs/nextjs/agents/nextjs-architect.md +331 -0
- package/packs/nextjs/agents/nextjs-modular.md +433 -0
- package/packs/nextjs/commands/component-create.md +398 -0
- package/packs/nextjs/rules/api-routes.md +129 -0
- package/packs/nextjs/rules/components.md +106 -0
- package/packs/nextjs/rules/hooks.md +132 -0
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +445 -0
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +399 -0
- package/packs/nextjs/skills/react-form-validator/SKILL.md +569 -0
- package/packs/nuxtjs/CHANGELOG.md +30 -0
- package/packs/nuxtjs/README.md +56 -0
- package/packs/nuxtjs/agents/frontend-tester.md +680 -0
- package/packs/nuxtjs/agents/frontend-visual.md +820 -0
- package/packs/nuxtjs/agents/nuxtjs-architect.md +537 -0
- package/packs/nuxtjs/commands/component-create.md +223 -0
- package/packs/nuxtjs/rules/components.md +101 -0
- package/packs/nuxtjs/rules/composables.md +118 -0
- package/packs/nuxtjs/rules/server-routes.md +127 -0
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +183 -0
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +196 -0
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +190 -0
- package/packs/onboard/CHANGELOG.md +22 -0
- package/packs/onboard/README.md +103 -0
- package/packs/onboard/agents/onboard-guide.md +118 -0
- package/packs/onboard/commands/onboard.md +313 -0
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +98 -0
- package/packs/tanstack-router/.claude-plugin/plugin.json +14 -0
- package/packs/tanstack-router/CHANGELOG.md +30 -0
- package/packs/tanstack-router/README.md +113 -0
- package/packs/tanstack-router/agents/tanstack-architect.md +173 -0
- package/packs/tanstack-router/agents/tanstack-builder.md +360 -0
- package/packs/tanstack-router/agents/tanstack-tester.md +454 -0
- package/packs/tanstack-router/commands/form-create.md +313 -0
- package/packs/tanstack-router/commands/query-create.md +263 -0
- package/packs/tanstack-router/commands/route-create.md +190 -0
- package/packs/tanstack-router/commands/table-create.md +413 -0
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +370 -0
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +346 -0
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +415 -0
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +425 -0
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +341 -0
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +359 -0
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +285 -0
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +351 -0
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +531 -0
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +428 -0
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +490 -0
- package/packs/worktree/.claude-plugin/plugin.json +19 -0
- package/packs/worktree/CHANGELOG.md +24 -0
- package/packs/worktree/README.md +110 -0
- package/packs/worktree/commands/wt.md +73 -0
- package/packs/worktree/scripts/wt.sh +396 -0
- package/packs/worktree/skills/worktree-manager/SKILL.md +68 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: TanStack Virtual Patterns
|
|
3
|
+
description: >-
|
|
4
|
+
Auto-enforce TanStack Virtual best practices for list virtualization. Activates
|
|
5
|
+
when implementing large lists, infinite scroll, virtualized grids, or performance
|
|
6
|
+
optimization for long lists in React applications.
|
|
7
|
+
version: 1.0.0
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# TanStack Virtual Patterns
|
|
11
|
+
|
|
12
|
+
This skill enforces TanStack Virtual best practices for efficient rendering of large lists.
|
|
13
|
+
|
|
14
|
+
## Basic Virtual List
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
18
|
+
import { useRef } from 'react'
|
|
19
|
+
|
|
20
|
+
interface VirtualListProps<T> {
|
|
21
|
+
items: T[]
|
|
22
|
+
renderItem: (item: T, index: number) => React.ReactNode
|
|
23
|
+
estimateSize?: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function VirtualList<T>({
|
|
27
|
+
items,
|
|
28
|
+
renderItem,
|
|
29
|
+
estimateSize = 50,
|
|
30
|
+
}: VirtualListProps<T>) {
|
|
31
|
+
const parentRef = useRef<HTMLDivElement>(null)
|
|
32
|
+
|
|
33
|
+
const virtualizer = useVirtualizer({
|
|
34
|
+
count: items.length,
|
|
35
|
+
getScrollElement: () => parentRef.current,
|
|
36
|
+
estimateSize: () => estimateSize,
|
|
37
|
+
overscan: 5,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
ref={parentRef}
|
|
43
|
+
className="h-[400px] overflow-auto"
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
style={{
|
|
47
|
+
height: `${virtualizer.getTotalSize()}px`,
|
|
48
|
+
width: '100%',
|
|
49
|
+
position: 'relative',
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{virtualizer.getVirtualItems().map((virtualItem) => (
|
|
53
|
+
<div
|
|
54
|
+
key={virtualItem.key}
|
|
55
|
+
style={{
|
|
56
|
+
position: 'absolute',
|
|
57
|
+
top: 0,
|
|
58
|
+
left: 0,
|
|
59
|
+
width: '100%',
|
|
60
|
+
height: `${virtualItem.size}px`,
|
|
61
|
+
transform: `translateY(${virtualItem.start}px)`,
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{renderItem(items[virtualItem.index], virtualItem.index)}
|
|
65
|
+
</div>
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Dynamic Size Virtual List
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
77
|
+
import { useRef, useCallback } from 'react'
|
|
78
|
+
|
|
79
|
+
export function DynamicVirtualList({ items }: { items: Post[] }) {
|
|
80
|
+
const parentRef = useRef<HTMLDivElement>(null)
|
|
81
|
+
|
|
82
|
+
const virtualizer = useVirtualizer({
|
|
83
|
+
count: items.length,
|
|
84
|
+
getScrollElement: () => parentRef.current,
|
|
85
|
+
estimateSize: () => 100, // Estimated height
|
|
86
|
+
measureElement: (element) => element.getBoundingClientRect().height,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div ref={parentRef} className="h-[600px] overflow-auto">
|
|
91
|
+
<div
|
|
92
|
+
style={{
|
|
93
|
+
height: `${virtualizer.getTotalSize()}px`,
|
|
94
|
+
position: 'relative',
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{virtualizer.getVirtualItems().map((virtualItem) => (
|
|
98
|
+
<div
|
|
99
|
+
key={virtualItem.key}
|
|
100
|
+
data-index={virtualItem.index}
|
|
101
|
+
ref={virtualizer.measureElement}
|
|
102
|
+
style={{
|
|
103
|
+
position: 'absolute',
|
|
104
|
+
top: 0,
|
|
105
|
+
left: 0,
|
|
106
|
+
width: '100%',
|
|
107
|
+
transform: `translateY(${virtualItem.start}px)`,
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<PostCard post={items[virtualItem.index]} />
|
|
111
|
+
</div>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Virtual Grid
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
123
|
+
import { useRef } from 'react'
|
|
124
|
+
|
|
125
|
+
interface VirtualGridProps<T> {
|
|
126
|
+
items: T[]
|
|
127
|
+
columns: number
|
|
128
|
+
renderItem: (item: T, index: number) => React.ReactNode
|
|
129
|
+
rowHeight?: number
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function VirtualGrid<T>({
|
|
133
|
+
items,
|
|
134
|
+
columns,
|
|
135
|
+
renderItem,
|
|
136
|
+
rowHeight = 200,
|
|
137
|
+
}: VirtualGridProps<T>) {
|
|
138
|
+
const parentRef = useRef<HTMLDivElement>(null)
|
|
139
|
+
const rowCount = Math.ceil(items.length / columns)
|
|
140
|
+
|
|
141
|
+
const rowVirtualizer = useVirtualizer({
|
|
142
|
+
count: rowCount,
|
|
143
|
+
getScrollElement: () => parentRef.current,
|
|
144
|
+
estimateSize: () => rowHeight,
|
|
145
|
+
overscan: 2,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div ref={parentRef} className="h-[600px] overflow-auto">
|
|
150
|
+
<div
|
|
151
|
+
style={{
|
|
152
|
+
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
153
|
+
position: 'relative',
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
|
157
|
+
const startIndex = virtualRow.index * columns
|
|
158
|
+
const rowItems = items.slice(startIndex, startIndex + columns)
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<div
|
|
162
|
+
key={virtualRow.key}
|
|
163
|
+
style={{
|
|
164
|
+
position: 'absolute',
|
|
165
|
+
top: 0,
|
|
166
|
+
left: 0,
|
|
167
|
+
width: '100%',
|
|
168
|
+
height: `${virtualRow.size}px`,
|
|
169
|
+
transform: `translateY(${virtualRow.start}px)`,
|
|
170
|
+
display: 'grid',
|
|
171
|
+
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
|
172
|
+
gap: '1rem',
|
|
173
|
+
}}
|
|
174
|
+
>
|
|
175
|
+
{rowItems.map((item, i) => (
|
|
176
|
+
<div key={startIndex + i}>
|
|
177
|
+
{renderItem(item, startIndex + i)}
|
|
178
|
+
</div>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
)
|
|
182
|
+
})}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Infinite Scroll with Virtual List
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
193
|
+
import { useInfiniteQuery } from '@tanstack/react-query'
|
|
194
|
+
import { useRef, useEffect } from 'react'
|
|
195
|
+
import { queryKeys } from '@/lib/query-keys'
|
|
196
|
+
|
|
197
|
+
export function InfiniteVirtualList() {
|
|
198
|
+
const parentRef = useRef<HTMLDivElement>(null)
|
|
199
|
+
|
|
200
|
+
const {
|
|
201
|
+
data,
|
|
202
|
+
fetchNextPage,
|
|
203
|
+
hasNextPage,
|
|
204
|
+
isFetchingNextPage,
|
|
205
|
+
} = useInfiniteQuery({
|
|
206
|
+
queryKey: queryKeys.posts.list({ infinite: true }),
|
|
207
|
+
queryFn: ({ pageParam = 1 }) => postApi.getPosts({ page: pageParam }),
|
|
208
|
+
getNextPageParam: (lastPage) =>
|
|
209
|
+
lastPage.hasMore ? lastPage.nextPage : undefined,
|
|
210
|
+
initialPageParam: 1,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const allItems = data?.pages.flatMap((page) => page.items) ?? []
|
|
214
|
+
|
|
215
|
+
const virtualizer = useVirtualizer({
|
|
216
|
+
count: hasNextPage ? allItems.length + 1 : allItems.length,
|
|
217
|
+
getScrollElement: () => parentRef.current,
|
|
218
|
+
estimateSize: () => 80,
|
|
219
|
+
overscan: 5,
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
const virtualItems = virtualizer.getVirtualItems()
|
|
223
|
+
|
|
224
|
+
// Load more when reaching the end
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
const lastItem = virtualItems[virtualItems.length - 1]
|
|
227
|
+
if (!lastItem) return
|
|
228
|
+
|
|
229
|
+
if (
|
|
230
|
+
lastItem.index >= allItems.length - 1 &&
|
|
231
|
+
hasNextPage &&
|
|
232
|
+
!isFetchingNextPage
|
|
233
|
+
) {
|
|
234
|
+
fetchNextPage()
|
|
235
|
+
}
|
|
236
|
+
}, [virtualItems, hasNextPage, isFetchingNextPage, allItems.length, fetchNextPage])
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<div ref={parentRef} className="h-[600px] overflow-auto">
|
|
240
|
+
<div
|
|
241
|
+
style={{
|
|
242
|
+
height: `${virtualizer.getTotalSize()}px`,
|
|
243
|
+
position: 'relative',
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
{virtualItems.map((virtualItem) => {
|
|
247
|
+
const isLoader = virtualItem.index >= allItems.length
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div
|
|
251
|
+
key={virtualItem.key}
|
|
252
|
+
style={{
|
|
253
|
+
position: 'absolute',
|
|
254
|
+
top: 0,
|
|
255
|
+
left: 0,
|
|
256
|
+
width: '100%',
|
|
257
|
+
height: `${virtualItem.size}px`,
|
|
258
|
+
transform: `translateY(${virtualItem.start}px)`,
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
{isLoader ? (
|
|
262
|
+
<div className="flex items-center justify-center h-full">
|
|
263
|
+
Loading more...
|
|
264
|
+
</div>
|
|
265
|
+
) : (
|
|
266
|
+
<PostCard post={allItems[virtualItem.index]} />
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
269
|
+
)
|
|
270
|
+
})}
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Horizontal Virtual List
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
281
|
+
import { useRef } from 'react'
|
|
282
|
+
|
|
283
|
+
export function HorizontalVirtualList({ items }: { items: Image[] }) {
|
|
284
|
+
const parentRef = useRef<HTMLDivElement>(null)
|
|
285
|
+
|
|
286
|
+
const virtualizer = useVirtualizer({
|
|
287
|
+
horizontal: true,
|
|
288
|
+
count: items.length,
|
|
289
|
+
getScrollElement: () => parentRef.current,
|
|
290
|
+
estimateSize: () => 200,
|
|
291
|
+
overscan: 3,
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<div ref={parentRef} className="w-full overflow-x-auto">
|
|
296
|
+
<div
|
|
297
|
+
style={{
|
|
298
|
+
width: `${virtualizer.getTotalSize()}px`,
|
|
299
|
+
height: '200px',
|
|
300
|
+
position: 'relative',
|
|
301
|
+
}}
|
|
302
|
+
>
|
|
303
|
+
{virtualizer.getVirtualItems().map((virtualItem) => (
|
|
304
|
+
<div
|
|
305
|
+
key={virtualItem.key}
|
|
306
|
+
style={{
|
|
307
|
+
position: 'absolute',
|
|
308
|
+
top: 0,
|
|
309
|
+
left: 0,
|
|
310
|
+
height: '100%',
|
|
311
|
+
width: `${virtualItem.size}px`,
|
|
312
|
+
transform: `translateX(${virtualItem.start}px)`,
|
|
313
|
+
}}
|
|
314
|
+
>
|
|
315
|
+
<img
|
|
316
|
+
src={items[virtualItem.index].url}
|
|
317
|
+
alt={items[virtualItem.index].alt}
|
|
318
|
+
className="h-full w-full object-cover"
|
|
319
|
+
/>
|
|
320
|
+
</div>
|
|
321
|
+
))}
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Virtual Table (with TanStack Table)
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
332
|
+
import { useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-table'
|
|
333
|
+
import { useRef } from 'react'
|
|
334
|
+
|
|
335
|
+
export function VirtualTable<T>({
|
|
336
|
+
data,
|
|
337
|
+
columns,
|
|
338
|
+
}: {
|
|
339
|
+
data: T[]
|
|
340
|
+
columns: ColumnDef<T>[]
|
|
341
|
+
}) {
|
|
342
|
+
const parentRef = useRef<HTMLDivElement>(null)
|
|
343
|
+
|
|
344
|
+
const table = useReactTable({
|
|
345
|
+
data,
|
|
346
|
+
columns,
|
|
347
|
+
getCoreRowModel: getCoreRowModel(),
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
const { rows } = table.getRowModel()
|
|
351
|
+
|
|
352
|
+
const virtualizer = useVirtualizer({
|
|
353
|
+
count: rows.length,
|
|
354
|
+
getScrollElement: () => parentRef.current,
|
|
355
|
+
estimateSize: () => 50,
|
|
356
|
+
overscan: 10,
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<div ref={parentRef} className="h-[600px] overflow-auto">
|
|
361
|
+
<table className="w-full">
|
|
362
|
+
<thead className="sticky top-0 bg-white z-10">
|
|
363
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
364
|
+
<tr key={headerGroup.id}>
|
|
365
|
+
{headerGroup.headers.map((header) => (
|
|
366
|
+
<th key={header.id}>
|
|
367
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
368
|
+
</th>
|
|
369
|
+
))}
|
|
370
|
+
</tr>
|
|
371
|
+
))}
|
|
372
|
+
</thead>
|
|
373
|
+
<tbody>
|
|
374
|
+
<tr style={{ height: `${virtualizer.getTotalSize()}px` }}>
|
|
375
|
+
<td colSpan={columns.length} style={{ padding: 0 }}>
|
|
376
|
+
<div style={{ position: 'relative' }}>
|
|
377
|
+
{virtualizer.getVirtualItems().map((virtualRow) => {
|
|
378
|
+
const row = rows[virtualRow.index]
|
|
379
|
+
return (
|
|
380
|
+
<div
|
|
381
|
+
key={row.id}
|
|
382
|
+
style={{
|
|
383
|
+
position: 'absolute',
|
|
384
|
+
top: 0,
|
|
385
|
+
left: 0,
|
|
386
|
+
width: '100%',
|
|
387
|
+
height: `${virtualRow.size}px`,
|
|
388
|
+
transform: `translateY(${virtualRow.start}px)`,
|
|
389
|
+
display: 'flex',
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
{row.getVisibleCells().map((cell) => (
|
|
393
|
+
<div key={cell.id} className="flex-1 px-4 py-2">
|
|
394
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
395
|
+
</div>
|
|
396
|
+
))}
|
|
397
|
+
</div>
|
|
398
|
+
)
|
|
399
|
+
})}
|
|
400
|
+
</div>
|
|
401
|
+
</td>
|
|
402
|
+
</tr>
|
|
403
|
+
</tbody>
|
|
404
|
+
</table>
|
|
405
|
+
</div>
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Scroll to Index
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
export function ScrollableVirtualList({ items }: { items: Post[] }) {
|
|
414
|
+
const parentRef = useRef<HTMLDivElement>(null)
|
|
415
|
+
|
|
416
|
+
const virtualizer = useVirtualizer({
|
|
417
|
+
count: items.length,
|
|
418
|
+
getScrollElement: () => parentRef.current,
|
|
419
|
+
estimateSize: () => 50,
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
const scrollToItem = (index: number) => {
|
|
423
|
+
virtualizer.scrollToIndex(index, { align: 'center', behavior: 'smooth' })
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return (
|
|
427
|
+
<div>
|
|
428
|
+
<div className="flex gap-2 mb-4">
|
|
429
|
+
<button onClick={() => scrollToItem(0)}>First</button>
|
|
430
|
+
<button onClick={() => scrollToItem(Math.floor(items.length / 2))}>
|
|
431
|
+
Middle
|
|
432
|
+
</button>
|
|
433
|
+
<button onClick={() => scrollToItem(items.length - 1)}>Last</button>
|
|
434
|
+
</div>
|
|
435
|
+
<div ref={parentRef} className="h-[400px] overflow-auto">
|
|
436
|
+
{/* Virtual list content */}
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Conventions to Enforce
|
|
444
|
+
|
|
445
|
+
1. **Ref for scroll element** - Always use `useRef` for parent container
|
|
446
|
+
2. **Fixed height container** - Parent must have defined height/overflow
|
|
447
|
+
3. **Overscan** - Include 3-5 items for smooth scrolling
|
|
448
|
+
4. **Absolute positioning** - Use transform for item placement
|
|
449
|
+
5. **Stable keys** - Use virtualItem.key, not index
|
|
450
|
+
6. **Dynamic sizing** - Use `measureElement` for variable heights
|
|
451
|
+
7. **Combine with Query** - Use infinite queries for data loading
|
|
452
|
+
|
|
453
|
+
## Anti-Patterns to Block
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
// ❌ WRONG: No fixed height container
|
|
457
|
+
<div ref={parentRef}>
|
|
458
|
+
{virtualItems.map(...)}
|
|
459
|
+
</div>
|
|
460
|
+
|
|
461
|
+
// ✅ CORRECT: Fixed height with overflow
|
|
462
|
+
<div ref={parentRef} className="h-[400px] overflow-auto">
|
|
463
|
+
{virtualItems.map(...)}
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
// ❌ WRONG: Using index as key
|
|
467
|
+
{virtualItems.map((item, index) => (
|
|
468
|
+
<div key={index}>...</div>
|
|
469
|
+
))}
|
|
470
|
+
|
|
471
|
+
// ✅ CORRECT: Using virtualItem.key
|
|
472
|
+
{virtualItems.map((virtualItem) => (
|
|
473
|
+
<div key={virtualItem.key}>...</div>
|
|
474
|
+
))}
|
|
475
|
+
|
|
476
|
+
// ❌ WRONG: No overscan
|
|
477
|
+
const virtualizer = useVirtualizer({
|
|
478
|
+
count: items.length,
|
|
479
|
+
getScrollElement: () => parentRef.current,
|
|
480
|
+
estimateSize: () => 50,
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
// ✅ CORRECT: Include overscan
|
|
484
|
+
const virtualizer = useVirtualizer({
|
|
485
|
+
count: items.length,
|
|
486
|
+
getScrollElement: () => parentRef.current,
|
|
487
|
+
estimateSize: () => 50,
|
|
488
|
+
overscan: 5,
|
|
489
|
+
})
|
|
490
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "worktree",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Git worktree manager for parallel development with automatic env copying and dependency installation",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Smicolon",
|
|
7
|
+
"email": "dev@smicolon.com",
|
|
8
|
+
"url": "https://github.com/smicolon"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": "https://github.com/smicolon/ai-kit",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"git",
|
|
14
|
+
"worktree",
|
|
15
|
+
"parallel-development",
|
|
16
|
+
"branch-management",
|
|
17
|
+
"monorepo"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to smi-worktree will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Renamed from `smi-worktree` to `worktree` as part of ai-kit migration
|
|
9
|
+
- Moved from `plugins/smi-worktree/` to `packs/worktree/`
|
|
10
|
+
|
|
11
|
+
## [0.1.0] - 2026-01-17
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Initial release
|
|
16
|
+
- `/wt create` command with auto-setup (env copying, dep install)
|
|
17
|
+
- `/wt list` command to show all worktrees
|
|
18
|
+
- `/wt remove` command with optional branch deletion
|
|
19
|
+
- `/wt open` command for Cursor/VS Code integration
|
|
20
|
+
- Short aliases: `c`, `ls`, `rm`, `o`
|
|
21
|
+
- Package manager detection (bun, pnpm, yarn, npm)
|
|
22
|
+
- Monorepo detection (workspaces, turbo, nx, lerna)
|
|
23
|
+
- Nested `.env*` file copying for monorepos
|
|
24
|
+
- worktree-manager skill for auto-triggering
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# worktree
|
|
2
|
+
|
|
3
|
+
Git worktree manager for parallel development with automatic environment setup.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Sibling naming**: `project--branch-name/` convention
|
|
8
|
+
- **Auto env copy**: Copies `.env*` from root and nested directories
|
|
9
|
+
- **Package manager detection**: bun → pnpm → yarn → npm
|
|
10
|
+
- **Monorepo aware**: Detects workspaces, turbo, nx, lerna
|
|
11
|
+
- **Editor integration**: Open in Cursor, Antigravity, or VS Code
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
/plugin install worktree
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Create Worktree
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
/wt create feature/authentication
|
|
25
|
+
/wt c feat-payments # short form
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Creates worktree at `~/code/project--feature-authentication/` with:
|
|
29
|
+
- All `.env*` files copied
|
|
30
|
+
- Dependencies installed
|
|
31
|
+
|
|
32
|
+
### List Worktrees
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
/wt list
|
|
36
|
+
/wt ls # short form
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Remove Worktree
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
/wt remove feature/authentication
|
|
43
|
+
/wt rm feat-payments -d # also delete branch
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Open in Editor
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
/wt open feat-payments # auto-detect
|
|
50
|
+
/wt o feat-payments # short form
|
|
51
|
+
/wt o feat-payments --cursor # or -c
|
|
52
|
+
/wt o feat-payments --agy # or -a
|
|
53
|
+
/wt o feat-payments --code # or -v
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Set Default Editor
|
|
57
|
+
|
|
58
|
+
Add to your shell profile (`~/.zshrc` or `~/.bashrc`):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
export WT_EDITOR=agy # cursor, agy, or code
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Priority: flag > `WT_EDITOR` > auto-detect (Cursor → Antigravity → VS Code)
|
|
65
|
+
|
|
66
|
+
## Naming Convention
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
~/code/retail-plus/ # main repo
|
|
70
|
+
~/code/retail-plus--feat-auth/ # worktree (sibling with --)
|
|
71
|
+
~/code/retail-plus--fix-bug-123/ # another worktree
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Auto-Setup Details
|
|
75
|
+
|
|
76
|
+
### Environment Files
|
|
77
|
+
|
|
78
|
+
Copies from:
|
|
79
|
+
- Root: `.env`, `.env.local`, `.env.development`, etc.
|
|
80
|
+
- Nested: `apps/*/.env*`, `packages/*/.env*`, `services/*/.env*`
|
|
81
|
+
|
|
82
|
+
### Package Manager Detection
|
|
83
|
+
|
|
84
|
+
Checks in order:
|
|
85
|
+
1. `bun.lockb` or `bun.lock` → `bun install`
|
|
86
|
+
2. `pnpm-lock.yaml` → `pnpm install`
|
|
87
|
+
3. `yarn.lock` → `yarn install`
|
|
88
|
+
4. `package-lock.json` or `package.json` → `npm install`
|
|
89
|
+
|
|
90
|
+
### Monorepo Detection
|
|
91
|
+
|
|
92
|
+
Detects via:
|
|
93
|
+
- `pnpm-workspace.yaml`
|
|
94
|
+
- `turbo.json`
|
|
95
|
+
- `nx.json`
|
|
96
|
+
- `lerna.json`
|
|
97
|
+
- `"workspaces"` in `package.json`
|
|
98
|
+
|
|
99
|
+
## Skill Triggers
|
|
100
|
+
|
|
101
|
+
The worktree-manager skill auto-activates when you mention:
|
|
102
|
+
- "worktree" or "git worktree"
|
|
103
|
+
- "parallel development"
|
|
104
|
+
- "feature branch setup"
|
|
105
|
+
- "work on multiple branches"
|
|
106
|
+
- "separate workspace for branch"
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wt
|
|
3
|
+
description: Git worktree manager - create, list, remove, and open worktrees with automatic env copying and dependency installation
|
|
4
|
+
argument-hint: '<command> [branch] [options]'
|
|
5
|
+
allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/wt.sh:*)"]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Git Worktree Manager
|
|
9
|
+
|
|
10
|
+
Manage git worktrees with automatic setup for parallel development.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
/wt <command> [args]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
| Command | Alias | Description |
|
|
21
|
+
|---------|-------|-------------|
|
|
22
|
+
| `create <branch>` | `c` | Create worktree, copy .env files, install deps |
|
|
23
|
+
| `list` | `ls` | List all worktrees for current repo |
|
|
24
|
+
| `remove <branch>` | `rm` | Remove worktree (add `-d` to delete branch) |
|
|
25
|
+
| `open <branch> [--editor]` | `o` | Open worktree (--cursor\|-c, --agy\|-a, --code\|-v) |
|
|
26
|
+
|
|
27
|
+
## Instructions
|
|
28
|
+
|
|
29
|
+
Run the worktree manager script:
|
|
30
|
+
|
|
31
|
+
```!
|
|
32
|
+
"${CLAUDE_PLUGIN_ROOT}/scripts/wt.sh" $ARGUMENTS
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Create worktree for new feature
|
|
39
|
+
/wt create feature/authentication
|
|
40
|
+
|
|
41
|
+
# Short form
|
|
42
|
+
/wt c feat-payments
|
|
43
|
+
|
|
44
|
+
# List all worktrees
|
|
45
|
+
/wt ls
|
|
46
|
+
|
|
47
|
+
# Remove worktree
|
|
48
|
+
/wt rm feature/authentication
|
|
49
|
+
|
|
50
|
+
# Remove worktree AND delete branch
|
|
51
|
+
/wt rm feat-payments -d
|
|
52
|
+
|
|
53
|
+
# Open in editor (auto-detect)
|
|
54
|
+
/wt o feat-payments
|
|
55
|
+
|
|
56
|
+
# Open in specific editor
|
|
57
|
+
/wt o feat-payments --agy
|
|
58
|
+
/wt o feat-payments -c
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Naming Convention
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
~/code/retail-plus/ → main repo
|
|
65
|
+
~/code/retail-plus--feat-auth/ → worktree (sibling with --)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Auto-Setup on Create
|
|
69
|
+
|
|
70
|
+
1. Copies all `.env*` files from root
|
|
71
|
+
2. Copies nested `.env*` from `apps/*/`, `packages/*/`, `services/*/`
|
|
72
|
+
3. Detects package manager (bun → pnpm → yarn → npm)
|
|
73
|
+
4. Runs install at root (monorepo-aware)
|