@smicolon/ai-kit 0.3.1 → 0.4.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/README.md +73 -40
- package/dist/index.js +260 -126
- package/package.json +5 -5
- package/.claude-plugin/marketplace.json +0 -373
- package/packs/architect/CHANGELOG.md +0 -17
- package/packs/architect/README.md +0 -58
- package/packs/architect/agents/system-architect.md +0 -768
- package/packs/architect/commands/diagram-create.md +0 -300
- package/packs/better-auth/.mcp.json +0 -14
- package/packs/better-auth/CHANGELOG.md +0 -26
- package/packs/better-auth/README.md +0 -125
- package/packs/better-auth/agents/auth-architect.md +0 -278
- package/packs/better-auth/commands/auth-provider-add.md +0 -265
- package/packs/better-auth/commands/auth-setup.md +0 -298
- package/packs/better-auth/skills/auth-security/SKILL.md +0 -425
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +0 -455
- package/packs/dev-loop/CHANGELOG.md +0 -69
- package/packs/dev-loop/README.md +0 -155
- package/packs/dev-loop/commands/cancel-dev.md +0 -21
- package/packs/dev-loop/commands/dev-loop.md +0 -72
- package/packs/dev-loop/commands/dev-plan.md +0 -351
- package/packs/dev-loop/hooks/hooks.json +0 -15
- package/packs/dev-loop/hooks/stop-hook.sh +0 -178
- package/packs/dev-loop/scripts/setup-dev-loop.sh +0 -194
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +0 -249
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +0 -874
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +0 -260
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +0 -275
- package/packs/django/CHANGELOG.md +0 -39
- package/packs/django/README.md +0 -92
- package/packs/django/agents/django-architect.md +0 -182
- package/packs/django/agents/django-builder.md +0 -250
- package/packs/django/agents/django-feature-based.md +0 -420
- package/packs/django/agents/django-reviewer.md +0 -253
- package/packs/django/agents/django-tester.md +0 -230
- package/packs/django/commands/api-endpoint.md +0 -285
- package/packs/django/commands/model-create.md +0 -178
- package/packs/django/commands/test-generate.md +0 -325
- package/packs/django/rules/migrations.md +0 -138
- package/packs/django/rules/models.md +0 -167
- package/packs/django/rules/serializers.md +0 -126
- package/packs/django/rules/services.md +0 -131
- package/packs/django/rules/tests.md +0 -140
- package/packs/django/rules/views.md +0 -102
- package/packs/django/skills/import-convention-enforcer/SKILL.md +0 -226
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +0 -343
- package/packs/django/skills/migration-safety-checker/SKILL.md +0 -375
- package/packs/django/skills/model-entity-validator/SKILL.md +0 -298
- package/packs/django/skills/performance-optimizer/SKILL.md +0 -447
- package/packs/django/skills/red-phase-verifier/SKILL.md +0 -180
- package/packs/django/skills/security-first-validator/SKILL.md +0 -435
- package/packs/django/skills/test-coverage-advisor/SKILL.md +0 -394
- package/packs/django/skills/test-validity-checker/SKILL.md +0 -194
- package/packs/failure-log/CHANGELOG.md +0 -20
- package/packs/failure-log/README.md +0 -168
- package/packs/failure-log/commands/failure-add.md +0 -106
- package/packs/failure-log/commands/failure-list.md +0 -89
- package/packs/failure-log/hooks/hooks.json +0 -16
- package/packs/failure-log/hooks/scripts/inject-failures.sh +0 -64
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +0 -164
- package/packs/flutter/CHANGELOG.md +0 -19
- package/packs/flutter/README.md +0 -170
- package/packs/flutter/agents/flutter-architect.md +0 -166
- package/packs/flutter/agents/flutter-builder.md +0 -303
- package/packs/flutter/agents/release-manager.md +0 -355
- package/packs/flutter/commands/fastlane-setup.md +0 -188
- package/packs/flutter/commands/flutter-build.md +0 -90
- package/packs/flutter/commands/flutter-deploy.md +0 -133
- package/packs/flutter/commands/flutter-test.md +0 -117
- package/packs/flutter/commands/signing-setup.md +0 -209
- package/packs/flutter/hooks/hooks.json +0 -17
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +0 -193
- package/packs/flutter/skills/flutter-architecture/SKILL.md +0 -127
- package/packs/flutter/skills/store-publishing/SKILL.md +0 -163
- package/packs/hono/CHANGELOG.md +0 -19
- package/packs/hono/README.md +0 -143
- package/packs/hono/agents/hono-architect.md +0 -240
- package/packs/hono/agents/hono-builder.md +0 -285
- package/packs/hono/agents/hono-reviewer.md +0 -279
- package/packs/hono/agents/hono-tester.md +0 -346
- package/packs/hono/commands/middleware-create.md +0 -223
- package/packs/hono/commands/project-init.md +0 -306
- package/packs/hono/commands/route-create.md +0 -153
- package/packs/hono/commands/rpc-client.md +0 -263
- package/packs/hono/hooks/hooks.json +0 -4
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +0 -408
- package/packs/hono/skills/hono-patterns/SKILL.md +0 -309
- package/packs/hono/skills/rpc-typesafe/SKILL.md +0 -388
- package/packs/hono/skills/zod-validation/SKILL.md +0 -332
- package/packs/nestjs/CHANGELOG.md +0 -29
- package/packs/nestjs/README.md +0 -75
- package/packs/nestjs/agents/nestjs-architect.md +0 -402
- package/packs/nestjs/agents/nestjs-builder.md +0 -301
- package/packs/nestjs/agents/nestjs-tester.md +0 -437
- package/packs/nestjs/commands/module-create.md +0 -369
- package/packs/nestjs/rules/controllers.md +0 -92
- package/packs/nestjs/rules/dto.md +0 -124
- package/packs/nestjs/rules/entities.md +0 -102
- package/packs/nestjs/rules/services.md +0 -106
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +0 -389
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +0 -365
- package/packs/nextjs/CHANGELOG.md +0 -36
- package/packs/nextjs/README.md +0 -76
- package/packs/nextjs/agents/frontend-tester.md +0 -680
- package/packs/nextjs/agents/frontend-visual.md +0 -820
- package/packs/nextjs/agents/nextjs-architect.md +0 -331
- package/packs/nextjs/agents/nextjs-modular.md +0 -433
- package/packs/nextjs/commands/component-create.md +0 -398
- package/packs/nextjs/rules/api-routes.md +0 -129
- package/packs/nextjs/rules/components.md +0 -106
- package/packs/nextjs/rules/hooks.md +0 -132
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +0 -445
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +0 -399
- package/packs/nextjs/skills/react-form-validator/SKILL.md +0 -569
- package/packs/nuxtjs/CHANGELOG.md +0 -30
- package/packs/nuxtjs/README.md +0 -56
- package/packs/nuxtjs/agents/frontend-tester.md +0 -680
- package/packs/nuxtjs/agents/frontend-visual.md +0 -820
- package/packs/nuxtjs/agents/nuxtjs-architect.md +0 -537
- package/packs/nuxtjs/commands/component-create.md +0 -223
- package/packs/nuxtjs/rules/components.md +0 -101
- package/packs/nuxtjs/rules/composables.md +0 -118
- package/packs/nuxtjs/rules/server-routes.md +0 -127
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +0 -183
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +0 -196
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +0 -190
- package/packs/onboard/CHANGELOG.md +0 -22
- package/packs/onboard/README.md +0 -103
- package/packs/onboard/agents/onboard-guide.md +0 -118
- package/packs/onboard/commands/onboard.md +0 -313
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +0 -98
- package/packs/tanstack-router/CHANGELOG.md +0 -30
- package/packs/tanstack-router/README.md +0 -113
- package/packs/tanstack-router/agents/tanstack-architect.md +0 -173
- package/packs/tanstack-router/agents/tanstack-builder.md +0 -360
- package/packs/tanstack-router/agents/tanstack-tester.md +0 -454
- package/packs/tanstack-router/commands/form-create.md +0 -313
- package/packs/tanstack-router/commands/query-create.md +0 -263
- package/packs/tanstack-router/commands/route-create.md +0 -190
- package/packs/tanstack-router/commands/table-create.md +0 -413
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +0 -370
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +0 -346
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +0 -415
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +0 -425
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +0 -341
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +0 -359
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +0 -285
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +0 -351
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +0 -531
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +0 -428
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +0 -490
- package/packs/worktree/CHANGELOG.md +0 -45
- package/packs/worktree/README.md +0 -219
- package/packs/worktree/commands/wt.md +0 -93
- package/packs/worktree/scripts/wt.sh +0 -957
- package/packs/worktree/skills/worktree-manager/SKILL.md +0 -113
|
@@ -1,490 +0,0 @@
|
|
|
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
|
-
```
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to worktree will be documented in this file.
|
|
4
|
-
|
|
5
|
-
## [Unreleased]
|
|
6
|
-
|
|
7
|
-
## [0.2.0] - 2026-02-15
|
|
8
|
-
|
|
9
|
-
### Added
|
|
10
|
-
|
|
11
|
-
- `.worktreeinclude` config file — gitignore-style patterns for files to copy
|
|
12
|
-
- Auto-generates `.worktreeinclude` with sensible defaults on first `wt create`
|
|
13
|
-
- `[rewrite]` section — auto-suffixes DB_NAME, DATABASE_URL, COMPOSE_PROJECT_NAME per branch
|
|
14
|
-
- `{{BRANCH}}` template support for custom env var rewriting
|
|
15
|
-
- `[docker]` section — generates `docker-compose.worktree.yml` with port offsets
|
|
16
|
-
- Deterministic port offset via branch name hash (1-100 range)
|
|
17
|
-
- Custom compose file path via `file=` directive (supports monorepo nested paths)
|
|
18
|
-
- Auto-creates Postgres databases (Docker containers or local)
|
|
19
|
-
- `docker compose down` on `wt remove` before removing worktree
|
|
20
|
-
- Port mapping summary in create output
|
|
21
|
-
|
|
22
|
-
### Changed
|
|
23
|
-
|
|
24
|
-
- `wt create` now uses `.worktreeinclude` patterns instead of hardcoded `.env*` copying
|
|
25
|
-
- `wt remove` stops Docker containers and notes that databases are preserved
|
|
26
|
-
- `wt help` documents `.worktreeinclude` sections
|
|
27
|
-
|
|
28
|
-
### Removed
|
|
29
|
-
|
|
30
|
-
- Hardcoded `copy_env_files()` function (replaced by `.worktreeinclude` pattern matching)
|
|
31
|
-
|
|
32
|
-
## [0.1.0] - 2026-01-17
|
|
33
|
-
|
|
34
|
-
### Added
|
|
35
|
-
|
|
36
|
-
- Initial release
|
|
37
|
-
- `/wt create` command with auto-setup (env copying, dep install)
|
|
38
|
-
- `/wt list` command to show all worktrees
|
|
39
|
-
- `/wt remove` command with optional branch deletion
|
|
40
|
-
- `/wt open` command for Cursor/VS Code integration
|
|
41
|
-
- Short aliases: `c`, `ls`, `rm`, `o`
|
|
42
|
-
- Package manager detection (bun, pnpm, yarn, npm)
|
|
43
|
-
- Monorepo detection (workspaces, turbo, nx, lerna)
|
|
44
|
-
- Nested `.env*` file copying for monorepos
|
|
45
|
-
- worktree-manager skill for auto-triggering
|