ginskill-init 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +77 -0
  2. package/agents/developer.md +56 -0
  3. package/agents/frontend-design.md +69 -0
  4. package/agents/mobile-reviewer.md +36 -0
  5. package/agents/review-code.md +49 -0
  6. package/agents/security-scanner.md +50 -0
  7. package/agents/tester.md +72 -0
  8. package/bin/cli.js +226 -0
  9. package/package.json +20 -0
  10. package/skills/ai-asset-generator/SKILL.md +255 -0
  11. package/skills/ai-asset-generator/docs/gen-image.md +274 -0
  12. package/skills/ai-asset-generator/docs/genvideo.md +341 -0
  13. package/skills/ai-asset-generator/docs/remove-background.md +19 -0
  14. package/skills/ai-asset-generator/generate-credit-assets.mjs +180 -0
  15. package/skills/ai-asset-generator/generate-ginbrowser-assets.mjs +242 -0
  16. package/skills/ai-asset-generator/generate-sty-icon.mjs +149 -0
  17. package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
  18. package/skills/ai-asset-generator/lib/env.mjs +38 -0
  19. package/skills/ai-asset-generator/lib/kie-client.mjs +88 -0
  20. package/skills/ai-asset-generator/scripts/scaffold-generator.mjs +203 -0
  21. package/skills/ai-build-ai/SKILL.md +124 -0
  22. package/skills/ai-build-ai/docs/agent-teams.md +293 -0
  23. package/skills/ai-build-ai/docs/checkpointing.md +161 -0
  24. package/skills/ai-build-ai/docs/create-agent.md +399 -0
  25. package/skills/ai-build-ai/docs/create-mcp.md +395 -0
  26. package/skills/ai-build-ai/docs/create-skill.md +299 -0
  27. package/skills/ai-build-ai/docs/headless-mode.md +614 -0
  28. package/skills/ai-build-ai/docs/hooks.md +578 -0
  29. package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
  30. package/skills/ai-build-ai/docs/output-styles.md +208 -0
  31. package/skills/ai-build-ai/docs/overview.md +162 -0
  32. package/skills/ai-build-ai/docs/permissions.md +391 -0
  33. package/skills/ai-build-ai/docs/plugins.md +396 -0
  34. package/skills/ai-build-ai/docs/sandbox.md +262 -0
  35. package/skills/ai-build-ai/scripts/load-tutorial.sh +54 -0
  36. package/skills/icon-generator/SKILL.md +270 -0
  37. package/skills/mobile-app-review/SKILL.md +321 -0
  38. package/skills/mobile-app-review/references/apple-review.md +132 -0
  39. package/skills/mobile-app-review/references/google-play-review.md +203 -0
  40. package/skills/mongodb/SKILL.md +667 -0
  41. package/skills/mongodb/references/mongoose-patterns.md +368 -0
  42. package/skills/nestjs-architecture/SKILL.md +1086 -0
  43. package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
  44. package/skills/performance/SKILL.md +509 -0
  45. package/skills/react-fsd-architecture/SKILL.md +693 -0
  46. package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
  47. package/skills/react-query/SKILL.md +685 -0
  48. package/skills/react-query/references/query-patterns.md +365 -0
  49. package/skills/review-code/SKILL.md +321 -0
  50. package/skills/review-code/references/clean-code-principles.md +395 -0
  51. package/skills/review-code/references/frontend-patterns.md +136 -0
  52. package/skills/review-code/references/nestjs-patterns.md +184 -0
  53. package/skills/review-code/scripts/check-module.sh +201 -0
  54. package/skills/review-code/scripts/deep-scan.sh +604 -0
  55. package/skills/review-code/scripts/dep-check.sh +522 -0
  56. package/skills/review-code/scripts/detect-duplicates.sh +466 -0
  57. package/skills/review-code/scripts/format-check.sh +577 -0
  58. package/skills/review-code/scripts/run-review.sh +167 -0
  59. package/skills/review-code/scripts/scan-codebase.sh +152 -0
  60. package/skills/security-scanner/SKILL.md +327 -0
  61. package/skills/security-scanner/references/nestjs-security.md +260 -0
  62. package/skills/security-scanner/references/nextjs-security.md +201 -0
  63. package/skills/security-scanner/references/react-native-security.md +199 -0
  64. package/skills/security-scanner/scripts/security-scan.sh +478 -0
  65. package/skills/ui-ux-pro-max/SKILL.md +377 -0
  66. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  67. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  68. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  69. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  70. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  71. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  72. package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  73. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  74. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  75. package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  76. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  77. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  78. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  79. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  80. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  81. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  82. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  83. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  84. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  85. package/skills/ui-ux-pro-max/data/styles.csv +68 -0
  86. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  87. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  88. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  89. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  90. package/skills/ui-ux-pro-max/scripts/core.py +253 -0
  91. package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  92. package/skills/ui-ux-pro-max/scripts/search.py +114 -0
@@ -0,0 +1,747 @@
1
+ # FSD Advanced Patterns
2
+
3
+ Detailed reference for advanced Feature-Sliced Design patterns. The main SKILL.md covers the core methodology — this file provides composition patterns, state management integration, testing strategy, and real-world edge cases.
4
+
5
+ ## Composition Patterns
6
+
7
+ ### How layers compose together
8
+
9
+ The key to FSD is **composition at higher layers**. Lower layers are independent; higher layers wire them together.
10
+
11
+ ```
12
+ Page composes → Widgets + Features + Entities
13
+ Widget composes → Features + Entities
14
+ Feature uses → Entities + Shared
15
+ Entity uses → Shared only
16
+ ```
17
+
18
+ ### Example: Product page composition
19
+
20
+ ```typescript
21
+ // pages/product-detail/ui/ProductDetailPage.tsx
22
+ import { ProductInfo } from '@/widgets/product-info'
23
+ import { AddToCart } from '@/features/add-to-cart'
24
+ import { ProductReviews } from '@/widgets/product-reviews'
25
+ import { RecommendedProducts } from '@/widgets/recommended-products'
26
+
27
+ export const ProductDetailPage = ({ id }: { id: string }) => (
28
+ <div>
29
+ <ProductInfo productId={id} />
30
+ <AddToCart productId={id} />
31
+ <ProductReviews productId={id} />
32
+ <RecommendedProducts productId={id} />
33
+ </div>
34
+ )
35
+ ```
36
+
37
+ ```typescript
38
+ // widgets/product-info/ui/ProductInfo.tsx
39
+ import { ProductCard } from '@/entities/product'
40
+ import { FavoriteButton } from '@/features/toggle-favorite'
41
+ import { ShareButton } from '@/features/share-product'
42
+
43
+ export const ProductInfo = ({ productId }: { productId: string }) => {
44
+ // Widget composes entity UI with feature actions
45
+ return (
46
+ <ProductCard id={productId}>
47
+ <FavoriteButton productId={productId} />
48
+ <ShareButton productId={productId} />
49
+ </ProductCard>
50
+ )
51
+ }
52
+ ```
53
+
54
+ ### Render props / slots pattern
55
+
56
+ When an entity needs feature-level actions but can't import features (wrong direction), use composition via props:
57
+
58
+ ```typescript
59
+ // entities/todo/ui/TodoCard.tsx
60
+ interface TodoCardProps {
61
+ todo: Todo
62
+ actions?: React.ReactNode // slot for feature-level actions
63
+ }
64
+
65
+ export const TodoCard = ({ todo, actions }: TodoCardProps) => (
66
+ <div>
67
+ <h3>{todo.title}</h3>
68
+ <p>{todo.description}</p>
69
+ {actions && <div className="actions">{actions}</div>}
70
+ </div>
71
+ )
72
+ ```
73
+
74
+ ```typescript
75
+ // widgets/todo-list/ui/TodoList.tsx — composes entity + features
76
+ import { TodoCard } from '@/entities/todo'
77
+ import { ToggleButton } from '@/features/toggle-todo'
78
+ import { DeleteButton } from '@/features/delete-todo'
79
+
80
+ export const TodoList = ({ todos }: { todos: Todo[] }) => (
81
+ <ul>
82
+ {todos.map(todo => (
83
+ <TodoCard
84
+ key={todo.id}
85
+ todo={todo}
86
+ actions={
87
+ <>
88
+ <ToggleButton todoId={todo.id} />
89
+ <DeleteButton todoId={todo.id} />
90
+ </>
91
+ }
92
+ />
93
+ ))}
94
+ </ul>
95
+ )
96
+ ```
97
+
98
+ ## State Management Integration
99
+
100
+ ### Zustand with FSD
101
+
102
+ Each entity/feature owns its own store:
103
+
104
+ ```typescript
105
+ // entities/user/model/user.store.ts
106
+ import { create } from 'zustand'
107
+ import type { User } from './user.types'
108
+
109
+ interface UserStore {
110
+ user: User | null
111
+ setUser: (user: User) => void
112
+ clear: () => void
113
+ }
114
+
115
+ export const useUserStore = create<UserStore>((set) => ({
116
+ user: null,
117
+ setUser: (user) => set({ user }),
118
+ clear: () => set({ user: null }),
119
+ }))
120
+ ```
121
+
122
+ ```typescript
123
+ // entities/user/index.ts
124
+ export { useUserStore } from './model/user.store'
125
+ export type { User } from './model/user.types'
126
+ export { UserAvatar } from './ui/UserAvatar'
127
+ ```
128
+
129
+ ### React Query with FSD
130
+
131
+ Query hooks live in the entity/feature that owns the data:
132
+
133
+ ```typescript
134
+ // entities/product/api/product.api.ts
135
+ import { apiClient } from '@/shared/api'
136
+ import type { Product } from '../model/product.types'
137
+
138
+ export const productApi = {
139
+ getAll: (filters?: ProductFilters) =>
140
+ apiClient.get<Product[]>('/products', { params: filters }),
141
+ getById: (id: string) =>
142
+ apiClient.get<Product>(`/products/${id}`),
143
+ }
144
+ ```
145
+
146
+ ```typescript
147
+ // entities/product/model/product.queries.ts
148
+ import { useQuery } from '@tanstack/react-query'
149
+ import { productApi } from '../api/product.api'
150
+
151
+ const productKeys = {
152
+ all: ['products'] as const,
153
+ lists: () => [...productKeys.all, 'list'] as const,
154
+ list: (filters: ProductFilters) => [...productKeys.lists(), filters] as const,
155
+ details: () => [...productKeys.all, 'detail'] as const,
156
+ detail: (id: string) => [...productKeys.details(), id] as const,
157
+ }
158
+
159
+ export const useProducts = (filters?: ProductFilters) =>
160
+ useQuery({
161
+ queryKey: productKeys.list(filters ?? {}),
162
+ queryFn: () => productApi.getAll(filters),
163
+ })
164
+
165
+ export const useProduct = (id: string) =>
166
+ useQuery({
167
+ queryKey: productKeys.detail(id),
168
+ queryFn: () => productApi.getById(id),
169
+ enabled: !!id,
170
+ })
171
+ ```
172
+
173
+ ```typescript
174
+ // features/add-to-cart/model/useAddToCart.ts
175
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
176
+ import { cartApi } from '@/entities/cart'
177
+
178
+ export const useAddToCart = () => {
179
+ const queryClient = useQueryClient()
180
+ return useMutation({
181
+ mutationFn: cartApi.addItem,
182
+ onSuccess: () => {
183
+ queryClient.invalidateQueries({ queryKey: ['cart'] })
184
+ },
185
+ })
186
+ }
187
+ ```
188
+
189
+ ### Redux Toolkit with FSD
190
+
191
+ Each slice in Redux maps to an FSD entity or feature:
192
+
193
+ ```typescript
194
+ // entities/todo/model/todo.slice.ts
195
+ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
196
+ import { todoApi } from '../api/todo.api'
197
+
198
+ export const fetchTodos = createAsyncThunk('todos/fetch', todoApi.getAll)
199
+
200
+ const todoSlice = createSlice({
201
+ name: 'todos',
202
+ initialState: { items: [], status: 'idle' },
203
+ reducers: {},
204
+ extraReducers: (builder) => {
205
+ builder
206
+ .addCase(fetchTodos.pending, (state) => { state.status = 'loading' })
207
+ .addCase(fetchTodos.fulfilled, (state, action) => {
208
+ state.items = action.payload
209
+ state.status = 'idle'
210
+ })
211
+ },
212
+ })
213
+
214
+ export const todoReducer = todoSlice.reducer
215
+ ```
216
+
217
+ Root store lives in `app/`:
218
+
219
+ ```typescript
220
+ // app/store/index.ts
221
+ import { configureStore } from '@reduxjs/toolkit'
222
+ import { todoReducer } from '@/entities/todo'
223
+ import { userReducer } from '@/entities/user'
224
+
225
+ export const store = configureStore({
226
+ reducer: {
227
+ todos: todoReducer,
228
+ user: userReducer,
229
+ },
230
+ })
231
+ ```
232
+
233
+ ## Testing Strategy
234
+
235
+ ### Where tests live
236
+
237
+ Tests live **inside their slice**, next to the code they test:
238
+
239
+ ```
240
+ features/
241
+ ├── authentication/
242
+ │ ├── ui/
243
+ │ │ ├── LoginForm.tsx
244
+ │ │ └── LoginForm.test.tsx ← component test
245
+ │ ├── model/
246
+ │ │ ├── auth.store.ts
247
+ │ │ └── auth.store.test.ts ← unit test
248
+ │ ├── api/
249
+ │ │ └── auth.api.test.ts ← API mock test
250
+ │ └── index.ts
251
+ ```
252
+
253
+ Or use a `__tests__` segment:
254
+
255
+ ```
256
+ features/
257
+ ├── authentication/
258
+ │ ├── ui/
259
+ │ ├── model/
260
+ │ ├── __tests__/
261
+ │ │ ├── LoginForm.test.tsx
262
+ │ │ └── auth.store.test.ts
263
+ │ └── index.ts
264
+ ```
265
+
266
+ ### Test isolation
267
+
268
+ Tests should import from the **public API** just like any other consumer:
269
+
270
+ ```typescript
271
+ // features/authentication/__tests__/LoginForm.test.tsx
272
+ import { LoginForm } from '../index' // or '@/features/authentication'
273
+
274
+ // NOT from internal paths
275
+ // import { LoginForm } from '../ui/LoginForm' ← avoid
276
+ ```
277
+
278
+ ### Integration tests
279
+
280
+ Integration tests that span multiple slices live at the page or widget level:
281
+
282
+ ```
283
+ pages/
284
+ ├── home/
285
+ │ ├── ui/
286
+ │ │ └── HomePage.tsx
287
+ │ ├── __tests__/
288
+ │ │ └── HomePage.integration.test.tsx
289
+ │ └── index.ts
290
+ ```
291
+
292
+ ### E2E tests
293
+
294
+ E2E tests live outside the `src/` tree:
295
+
296
+ ```
297
+ e2e/
298
+ ├── authentication.spec.ts
299
+ ├── checkout.spec.ts
300
+ └── search.spec.ts
301
+ ```
302
+
303
+ ## Handling Shared Types
304
+
305
+ ### Types that belong in entities
306
+
307
+ ```typescript
308
+ // entities/user/model/user.types.ts
309
+ export interface User {
310
+ id: string
311
+ name: string
312
+ email: string
313
+ avatar?: string
314
+ }
315
+ ```
316
+
317
+ ### Types that belong in shared
318
+
319
+ Only generic, non-domain types:
320
+
321
+ ```typescript
322
+ // shared/types/index.ts
323
+ export interface PaginatedResponse<T> {
324
+ items: T[]
325
+ total: number
326
+ page: number
327
+ pageSize: number
328
+ }
329
+
330
+ export interface ApiError {
331
+ message: string
332
+ code: string
333
+ statusCode: number
334
+ }
335
+ ```
336
+
337
+ ### DTO types
338
+
339
+ DTOs (Data Transfer Objects) live in the `api` segment of the entity that owns them:
340
+
341
+ ```typescript
342
+ // entities/product/api/product.dto.ts
343
+ export interface CreateProductDto {
344
+ name: string
345
+ price: number
346
+ categoryId: string
347
+ }
348
+
349
+ export interface ProductResponseDto {
350
+ id: string
351
+ name: string
352
+ price: number
353
+ category: { id: string; name: string }
354
+ createdAt: string
355
+ }
356
+ ```
357
+
358
+ ## Real-World API Integration
359
+
360
+ Concrete examples using actual Styai/EasyCloset API endpoints for a log management admin dashboard.
361
+
362
+ ### Entity: `system-log`
363
+
364
+ ```typescript
365
+ // entities/system-log/model/system-log.types.ts
366
+ export type LogKind = 'APPLICATION' | 'ACCESS' | 'ERROR' | 'AUDIT' | 'SECURITY'
367
+ export type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL'
368
+
369
+ export interface SystemLog {
370
+ id: string
371
+ kind: LogKind
372
+ level: LogLevel
373
+ message: string
374
+ source: string
375
+ timestamp: string
376
+ metadata?: Record<string, unknown>
377
+ }
378
+
379
+ export interface LogFile {
380
+ filename: string
381
+ size: number
382
+ lastModified: string
383
+ }
384
+
385
+ export interface LogSearchParams {
386
+ type?: LogKind
387
+ level?: LogLevel
388
+ startDate?: string
389
+ endDate?: string
390
+ keyword?: string
391
+ page?: number
392
+ pageSize?: number
393
+ }
394
+ ```
395
+
396
+ ```typescript
397
+ // entities/system-log/api/system-log.api.ts
398
+ import { apiClient } from '@/shared/api'
399
+ import type { SystemLog, LogFile, LogSearchParams } from '../model/system-log.types'
400
+ import type { PaginatedResponse } from '@/shared/types'
401
+
402
+ export const systemLogApi = {
403
+ getTypes: () =>
404
+ apiClient.get<string[]>('/api/v1/system/logs/types'),
405
+
406
+ getFiles: (type: string) =>
407
+ apiClient.get<LogFile[]>(`/api/v1/system/logs/${type}/files`),
408
+
409
+ getContent: (type: string, filename: string, params?: { lines?: number }) =>
410
+ apiClient.get<string[]>(`/api/v1/system/logs/${type}/${filename}`, { params }),
411
+
412
+ search: (params: LogSearchParams) =>
413
+ apiClient.get<PaginatedResponse<SystemLog>>('/api/v1/system/logs/search', { params }),
414
+
415
+ download: (type: string, filename: string) =>
416
+ apiClient.get<Blob>(`/api/v1/system/logs/${type}/${filename}/download`, {
417
+ responseType: 'blob',
418
+ }),
419
+ }
420
+ ```
421
+
422
+ ```typescript
423
+ // entities/system-log/model/system-log.queries.ts
424
+ import { useQuery } from '@tanstack/react-query'
425
+ import { systemLogApi } from '../api/system-log.api'
426
+ import type { LogSearchParams } from './system-log.types'
427
+
428
+ export const systemLogKeys = {
429
+ all: ['system-logs'] as const,
430
+ types: () => [...systemLogKeys.all, 'types'] as const,
431
+ files: (type: string) => [...systemLogKeys.all, 'files', type] as const,
432
+ content: (type: string, filename: string) =>
433
+ [...systemLogKeys.all, 'content', type, filename] as const,
434
+ search: (params: LogSearchParams) =>
435
+ [...systemLogKeys.all, 'search', params] as const,
436
+ }
437
+
438
+ export const useLogTypes = () =>
439
+ useQuery({
440
+ queryKey: systemLogKeys.types(),
441
+ queryFn: () => systemLogApi.getTypes(),
442
+ })
443
+
444
+ export const useLogFiles = (type: string) =>
445
+ useQuery({
446
+ queryKey: systemLogKeys.files(type),
447
+ queryFn: () => systemLogApi.getFiles(type),
448
+ enabled: !!type,
449
+ })
450
+
451
+ export const useLogContent = (type: string, filename: string) =>
452
+ useQuery({
453
+ queryKey: systemLogKeys.content(type, filename),
454
+ queryFn: () => systemLogApi.getContent(type, filename),
455
+ enabled: !!type && !!filename,
456
+ })
457
+
458
+ export const useLogSearch = (params: LogSearchParams) =>
459
+ useQuery({
460
+ queryKey: systemLogKeys.search(params),
461
+ queryFn: () => systemLogApi.search(params),
462
+ })
463
+ ```
464
+
465
+ ```typescript
466
+ // entities/system-log/index.ts
467
+ export { LogTable } from './ui/LogTable'
468
+ export { LogLevelBadge } from './ui/LogLevelBadge'
469
+ export type { SystemLog, LogFile, LogKind, LogLevel, LogSearchParams } from './model/system-log.types'
470
+ export { useLogTypes, useLogFiles, useLogContent, useLogSearch, systemLogKeys } from './model/system-log.queries'
471
+ export { systemLogApi } from './api/system-log.api'
472
+ ```
473
+
474
+ ### Feature: `search-logs`
475
+
476
+ ```typescript
477
+ // features/search-logs/model/useLogSearchForm.ts
478
+ import { useState, useCallback } from 'react'
479
+ import type { LogKind, LogLevel, LogSearchParams } from '@/entities/system-log'
480
+
481
+ export const useLogSearchForm = () => {
482
+ const [filters, setFilters] = useState<LogSearchParams>({})
483
+
484
+ const updateFilter = useCallback(<K extends keyof LogSearchParams>(
485
+ key: K,
486
+ value: LogSearchParams[K],
487
+ ) => {
488
+ setFilters(prev => ({ ...prev, [key]: value }))
489
+ }, [])
490
+
491
+ const resetFilters = useCallback(() => setFilters({}), [])
492
+
493
+ return { filters, updateFilter, resetFilters }
494
+ }
495
+ ```
496
+
497
+ ```typescript
498
+ // features/search-logs/ui/LogSearchForm.tsx
499
+ import { useLogTypes } from '@/entities/system-log'
500
+ import type { LogKind, LogLevel, LogSearchParams } from '@/entities/system-log'
501
+ import { Input, Select, Button } from '@/shared/ui'
502
+
503
+ interface LogSearchFormProps {
504
+ filters: LogSearchParams
505
+ onFilterChange: <K extends keyof LogSearchParams>(key: K, value: LogSearchParams[K]) => void
506
+ onReset: () => void
507
+ }
508
+
509
+ export const LogSearchForm = ({ filters, onFilterChange, onReset }: LogSearchFormProps) => {
510
+ const { data: logTypes } = useLogTypes()
511
+
512
+ return (
513
+ <form className="flex gap-3 items-end flex-wrap">
514
+ <Select
515
+ label="Type"
516
+ value={filters.type ?? ''}
517
+ onChange={(v) => onFilterChange('type', v as LogKind)}
518
+ options={logTypes?.map(t => ({ label: t, value: t })) ?? []}
519
+ />
520
+ <Select
521
+ label="Level"
522
+ value={filters.level ?? ''}
523
+ onChange={(v) => onFilterChange('level', v as LogLevel)}
524
+ options={['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'].map(l => ({ label: l, value: l }))}
525
+ />
526
+ <Input
527
+ label="From"
528
+ type="date"
529
+ value={filters.startDate ?? ''}
530
+ onChange={(v) => onFilterChange('startDate', v)}
531
+ />
532
+ <Input
533
+ label="To"
534
+ type="date"
535
+ value={filters.endDate ?? ''}
536
+ onChange={(v) => onFilterChange('endDate', v)}
537
+ />
538
+ <Input
539
+ label="Keyword"
540
+ value={filters.keyword ?? ''}
541
+ onChange={(v) => onFilterChange('keyword', v)}
542
+ placeholder="Search log messages..."
543
+ />
544
+ <Button variant="ghost" onClick={onReset}>Reset</Button>
545
+ </form>
546
+ )
547
+ }
548
+ ```
549
+
550
+ ```typescript
551
+ // features/search-logs/index.ts
552
+ export { LogSearchForm } from './ui/LogSearchForm'
553
+ export { useLogSearchForm } from './model/useLogSearchForm'
554
+ ```
555
+
556
+ ### Feature: `download-log-file`
557
+
558
+ ```typescript
559
+ // features/download-log-file/model/useDownloadLog.ts
560
+ import { useMutation } from '@tanstack/react-query'
561
+ import { systemLogApi } from '@/entities/system-log'
562
+
563
+ export const useDownloadLog = () =>
564
+ useMutation({
565
+ mutationFn: ({ type, filename }: { type: string; filename: string }) =>
566
+ systemLogApi.download(type, filename),
567
+ onSuccess: (blob, { filename }) => {
568
+ const url = URL.createObjectURL(blob)
569
+ const a = document.createElement('a')
570
+ a.href = url
571
+ a.download = filename
572
+ a.click()
573
+ URL.revokeObjectURL(url)
574
+ },
575
+ })
576
+ ```
577
+
578
+ ```typescript
579
+ // features/download-log-file/ui/DownloadLogButton.tsx
580
+ import { Button } from '@/shared/ui'
581
+ import { useDownloadLog } from '../model/useDownloadLog'
582
+
583
+ interface DownloadLogButtonProps {
584
+ type: string
585
+ filename: string
586
+ }
587
+
588
+ export const DownloadLogButton = ({ type, filename }: DownloadLogButtonProps) => {
589
+ const { mutate, isPending } = useDownloadLog()
590
+
591
+ return (
592
+ <Button
593
+ variant="ghost"
594
+ size="sm"
595
+ disabled={isPending}
596
+ onClick={() => mutate({ type, filename })}
597
+ >
598
+ {isPending ? 'Downloading...' : 'Download'}
599
+ </Button>
600
+ )
601
+ }
602
+ ```
603
+
604
+ ```typescript
605
+ // features/download-log-file/index.ts
606
+ export { DownloadLogButton } from './ui/DownloadLogButton'
607
+ export { useDownloadLog } from './model/useDownloadLog'
608
+ ```
609
+
610
+ ### Widget: `log-viewer`
611
+
612
+ ```typescript
613
+ // widgets/log-viewer/ui/LogViewer.tsx
614
+ import { LogTable, useLogSearch } from '@/entities/system-log'
615
+ import { LogSearchForm, useLogSearchForm } from '@/features/search-logs'
616
+ import { DownloadLogButton } from '@/features/download-log-file'
617
+
618
+ export const LogViewer = () => {
619
+ const { filters, updateFilter, resetFilters } = useLogSearchForm()
620
+ const { data, isLoading } = useLogSearch(filters)
621
+
622
+ return (
623
+ <div className="space-y-4">
624
+ <LogSearchForm
625
+ filters={filters}
626
+ onFilterChange={updateFilter}
627
+ onReset={resetFilters}
628
+ />
629
+ <LogTable
630
+ logs={data?.items ?? []}
631
+ isLoading={isLoading}
632
+ actions={(log) => (
633
+ <DownloadLogButton type={log.kind} filename={`${log.id}.log`} />
634
+ )}
635
+ />
636
+ </div>
637
+ )
638
+ }
639
+ ```
640
+
641
+ ```typescript
642
+ // widgets/log-viewer/index.ts
643
+ export { LogViewer } from './ui/LogViewer'
644
+ ```
645
+
646
+ ### Page: `log-management`
647
+
648
+ ```typescript
649
+ // pages/@admin/log-management/ui/LogManagementPage.tsx
650
+ import { LogViewer } from '@/widgets/log-viewer'
651
+
652
+ export const LogManagementPage = () => (
653
+ <div className="container mx-auto py-6">
654
+ <h1 className="text-2xl font-bold mb-6">Log Management</h1>
655
+ <LogViewer />
656
+ </div>
657
+ )
658
+ ```
659
+
660
+ ```typescript
661
+ // pages/@admin/log-management/index.ts
662
+ export { LogManagementPage } from './ui/LogManagementPage'
663
+ ```
664
+
665
+ ### Shared: `client-logger`
666
+
667
+ A fire-and-forget service for sending client-side logs to `POST /api/v1/logs/ingest`:
668
+
669
+ ```typescript
670
+ // shared/lib/client-logger.ts
671
+ import { apiClient } from '@/shared/api'
672
+
673
+ type ClientLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'
674
+
675
+ interface ClientLogEntry {
676
+ level: ClientLogLevel
677
+ message: string
678
+ context?: Record<string, unknown>
679
+ timestamp?: string
680
+ }
681
+
682
+ const sendLog = (entry: ClientLogEntry) => {
683
+ const payload = {
684
+ ...entry,
685
+ timestamp: entry.timestamp ?? new Date().toISOString(),
686
+ userAgent: navigator.userAgent,
687
+ url: window.location.href,
688
+ }
689
+
690
+ // Fire-and-forget — don't block UI on logging
691
+ apiClient.post('/api/v1/logs/ingest', payload).catch(() => {
692
+ // Silently fail — logging should never break the app
693
+ })
694
+ }
695
+
696
+ export const clientLogger = {
697
+ debug: (message: string, context?: Record<string, unknown>) =>
698
+ sendLog({ level: 'debug', message, context }),
699
+ info: (message: string, context?: Record<string, unknown>) =>
700
+ sendLog({ level: 'info', message, context }),
701
+ warn: (message: string, context?: Record<string, unknown>) =>
702
+ sendLog({ level: 'warn', message, context }),
703
+ error: (message: string, context?: Record<string, unknown>) =>
704
+ sendLog({ level: 'error', message, context }),
705
+ fatal: (message: string, context?: Record<string, unknown>) =>
706
+ sendLog({ level: 'fatal', message, context }),
707
+ }
708
+ ```
709
+
710
+ ```typescript
711
+ // Usage from any layer:
712
+ import { clientLogger } from '@/shared/lib'
713
+
714
+ // In an error boundary
715
+ clientLogger.error('Unhandled render error', { component: 'App', error: err.message })
716
+
717
+ // In a feature
718
+ clientLogger.info('User completed checkout', { orderId: '123' })
719
+ ```
720
+
721
+ ## Monorepo / Multi-Package Patterns
722
+
723
+ For large projects, FSD layers can map to packages:
724
+
725
+ ```
726
+ packages/
727
+ ├── shared/ ← @myapp/shared
728
+ ├── entities/ ← @myapp/entities
729
+ ├── features/ ← @myapp/features
730
+ └── apps/
731
+ ├── web/ ← uses @myapp/* packages
732
+ └── mobile/ ← uses @myapp/* packages
733
+ ```
734
+
735
+ Each package maintains the same internal FSD structure.
736
+
737
+ ## When NOT to Use FSD
738
+
739
+ - **Very small projects** (< 10 components) — overhead isn't worth it
740
+ - **Prototypes / hackathons** — speed matters more than structure
741
+ - **Solo projects with no growth plan** — simpler flat structure is fine
742
+
743
+ FSD shines when:
744
+ - Multiple developers work on the same codebase
745
+ - The project will grow over months/years
746
+ - Features are added, removed, or modified frequently
747
+ - You want to prevent "spaghetti" imports as the project scales