symfonia-ai-tools 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 (74) hide show
  1. package/README.md +489 -0
  2. package/bin/cli.mjs +35 -0
  3. package/lib/installer.mjs +495 -0
  4. package/lib/questions.mjs +332 -0
  5. package/lib/ui.mjs +76 -0
  6. package/lib/utils.mjs +231 -0
  7. package/package.json +26 -0
  8. package/templates/base/CLAUDE.md +34 -0
  9. package/templates/base/_ai/_guidelines_header.md +70 -0
  10. package/templates/base/_ai/context/README.md +20 -0
  11. package/templates/base/_ai/prompts/codereview.prompt.md +324 -0
  12. package/templates/base/_ai/prompts/duplicate-code-analysis.prompt.md +128 -0
  13. package/templates/base/_ai/prompts/figma-analysis.prompt.md +155 -0
  14. package/templates/base/_ai/prompts/security-review.prompt.md +46 -0
  15. package/templates/base/_ai/skills/README.md +80 -0
  16. package/templates/base/_ai/skills/TEMPLATE.md +106 -0
  17. package/templates/base/_ai/skills/babysit-prs/SKILL.md +105 -0
  18. package/templates/base/_ai/skills/debug/SKILL.md +93 -0
  19. package/templates/base/_ai/skills/fill-worklogs/SKILL.md +158 -0
  20. package/templates/base/_ai/skills/hotfix/SKILL.md +52 -0
  21. package/templates/base/_ai/skills/jira-task/SKILL.md +170 -0
  22. package/templates/base/_ai/skills/my-prs/SKILL.md +78 -0
  23. package/templates/base/_ai/skills/pr-dashboard/SKILL.md +43 -0
  24. package/templates/base/_ai/skills/pr-prepare/SKILL.md +106 -0
  25. package/templates/base/_ai/skills/refactor/SKILL.md +87 -0
  26. package/templates/base/_ai/skills/write-tests/SKILL.md +109 -0
  27. package/templates/base/_claude/settings.local.json +37 -0
  28. package/templates/base/_cursor/rules/global.mdc +7 -0
  29. package/templates/base/_editorconfig +18 -0
  30. package/templates/base/_gemini/settings.json +3 -0
  31. package/templates/base/_github/copilot-instructions.md +1 -0
  32. package/templates/base/_github/pull_request_template.md +23 -0
  33. package/templates/base/_gitignore +22 -0
  34. package/templates/base/_junie/guidelines.md +1 -0
  35. package/templates/base/commit-instructions.md +92 -0
  36. package/templates/packs/docker/_ai/instructions/docker.instructions.md +193 -0
  37. package/templates/packs/docker/_guidelines.md +10 -0
  38. package/templates/packs/docker/pack.json +8 -0
  39. package/templates/packs/laravel/_ai/instructions/api-resource.instructions.md +251 -0
  40. package/templates/packs/laravel/_ai/instructions/module.instructions.md +133 -0
  41. package/templates/packs/laravel/_ai/instructions/service-repository.instructions.md +215 -0
  42. package/templates/packs/laravel/_ai/instructions/testing.instructions.md +278 -0
  43. package/templates/packs/laravel/_ai/skills/migration/SKILL.md +172 -0
  44. package/templates/packs/laravel/_ai/skills/new-endpoint/SKILL.md +165 -0
  45. package/templates/packs/laravel/_ai/skills/new-module/SKILL.md +208 -0
  46. package/templates/packs/laravel/_ai/skills/queued-job/SKILL.md +248 -0
  47. package/templates/packs/laravel/_ai/skills/testing-feature/SKILL.md +196 -0
  48. package/templates/packs/laravel/_ai/skills/testing-manual/SKILL.md +186 -0
  49. package/templates/packs/laravel/_ai/skills/testing-unit/SKILL.md +200 -0
  50. package/templates/packs/laravel/_guidelines.md +25 -0
  51. package/templates/packs/laravel/pack.json +6 -0
  52. package/templates/packs/playwright/_ai/instructions/playwright.instructions.md +219 -0
  53. package/templates/packs/playwright/_ai/skills/playwright/README.md +194 -0
  54. package/templates/packs/playwright/_ai/skills/playwright/SKILL.md +1245 -0
  55. package/templates/packs/playwright/_ai/skills/playwright-codereview/SKILL.md +642 -0
  56. package/templates/packs/playwright/_ai/skills/playwright-record/README.md +87 -0
  57. package/templates/packs/playwright/_ai/skills/playwright-record/SKILL.md +564 -0
  58. package/templates/packs/playwright/_guidelines.md +12 -0
  59. package/templates/packs/playwright/pack.json +9 -0
  60. package/templates/packs/storybook/_ai/instructions/storybook.instructions.md +181 -0
  61. package/templates/packs/storybook/pack.json +6 -0
  62. package/templates/packs/vitest/_ai/instructions/vitest.instructions.md +688 -0
  63. package/templates/packs/vitest/pack.json +6 -0
  64. package/templates/packs/vue3/_ai/instructions/api.instructions.md +163 -0
  65. package/templates/packs/vue3/_ai/instructions/coding-conventions.instructions.md +160 -0
  66. package/templates/packs/vue3/_ai/instructions/composables.instructions.md +218 -0
  67. package/templates/packs/vue3/_ai/instructions/forms.instructions.md +227 -0
  68. package/templates/packs/vue3/_ai/instructions/store.instructions.md +504 -0
  69. package/templates/packs/vue3/_ai/instructions/vue.instructions.md +339 -0
  70. package/templates/packs/vue3/_ai/skills/api-integration/SKILL.md +195 -0
  71. package/templates/packs/vue3/_ai/skills/new-component/SKILL.md +133 -0
  72. package/templates/packs/vue3/_ai/skills/new-module/SKILL.md +177 -0
  73. package/templates/packs/vue3/_guidelines.md +45 -0
  74. package/templates/packs/vue3/pack.json +11 -0
@@ -0,0 +1,163 @@
1
+ ---
2
+ applyTo: "**/*Service*.ts,**/*service*.ts,**/api/**,**/composables/useApi*"
3
+ ---
4
+
5
+ # API & Services - Instrukcje
6
+
7
+ ## Architektura warstwy API
8
+
9
+ ### Composable API (singleton)
10
+
11
+ ```typescript
12
+ // composables/useApi.ts
13
+ import axios, { type AxiosInstance, type AxiosError, type AxiosRequestConfig } from 'axios';
14
+
15
+ let apiInstance: AxiosInstance | null = null;
16
+
17
+ export function useApi() {
18
+ if (!apiInstance) {
19
+ apiInstance = axios.create({
20
+ baseURL: import.meta.env.VITE_API_URL || '{{API_BASE_URL}}',
21
+ timeout: Number(import.meta.env.VITE_API_TIMEOUT) || 30000,
22
+ headers: { 'Content-Type': 'application/json' },
23
+ });
24
+
25
+ setupInterceptors(apiInstance);
26
+ }
27
+
28
+ return {
29
+ get: <T>(url: string, config?: AxiosRequestConfig) => apiInstance!.get<T>(url, config),
30
+ post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => apiInstance!.post<T>(url, data, config),
31
+ put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => apiInstance!.put<T>(url, data, config),
32
+ patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => apiInstance!.patch<T>(url, data, config),
33
+ delete: <T>(url: string, config?: AxiosRequestConfig) => apiInstance!.delete<T>(url, config),
34
+ setAuthToken,
35
+ };
36
+ }
37
+ ```
38
+
39
+ ### Serwisy modulowe (functional pattern)
40
+
41
+ ```typescript
42
+ // modules/[feature]/services/Feature.service.ts
43
+ import { useApi } from '@/composables/useApi';
44
+
45
+ const api = useApi();
46
+ const BASE = '/features';
47
+
48
+ const FeatureService = {
49
+ getAll: () => api.get<IFeature[]>(BASE),
50
+ getById: (id: number) => api.get<IFeature>(`${BASE}/${id}`),
51
+ create: (data: IFeatureCreate) => api.post<IFeature>(BASE, data),
52
+ update: (id: number, data: IFeatureUpdate) => api.put<IFeature>(`${BASE}/${id}`, data),
53
+ delete: (id: number) => api.delete<void>(`${BASE}/${id}`),
54
+ };
55
+
56
+ export default FeatureService;
57
+ ```
58
+
59
+ ## Interceptory
60
+
61
+ ### Request interceptor
62
+
63
+ ```typescript
64
+ function setupInterceptors(instance: AxiosInstance) {
65
+ instance.interceptors.request.use((config) => {
66
+ const token = getAuthToken();
67
+ if (token) {
68
+ config.headers.Authorization = `Bearer ${token}`;
69
+ }
70
+
71
+ // Accept-Language z i18n
72
+ const locale = getCurrentLocale();
73
+ if (locale) {
74
+ config.headers['Accept-Language'] = locale;
75
+ }
76
+
77
+ return config;
78
+ });
79
+
80
+ instance.interceptors.response.use(
81
+ (response) => response,
82
+ (error: AxiosError) => handleApiError(error),
83
+ );
84
+ }
85
+ ```
86
+
87
+ ### Obsluga bledow
88
+
89
+ ```typescript
90
+ function handleApiError(error: AxiosError): Promise<never> {
91
+ const status = error.response?.status;
92
+
93
+ switch (status) {
94
+ case 400: notify('error', 'Nieprawidlowe dane'); break;
95
+ case 401: redirectToLogin(); break;
96
+ case 403: notify('error', 'Brak uprawnien'); break;
97
+ case 404: notify('error', 'Nie znaleziono zasobu'); break;
98
+ case 409: notify('error', 'Konflikt danych'); break;
99
+ case 422: break; // Walidacja - obslugiwana przez formularz
100
+ case 500: notify('error', 'Blad serwera'); break;
101
+ default: notify('error', 'Wystapil nieoczekiwany blad');
102
+ }
103
+
104
+ return Promise.reject(error);
105
+ }
106
+ ```
107
+
108
+ ## Opcja returnErrors
109
+
110
+ Dla formularzy - zwraca bledy zamiast null:
111
+
112
+ ```typescript
113
+ // W serwisie
114
+ const FeatureService = {
115
+ create: (data: IFeatureCreate, returnErrors = false) => {
116
+ return api.post<IFeature>('/features', data)
117
+ .catch((error: AxiosError) => {
118
+ if (returnErrors) return error;
119
+ return null;
120
+ });
121
+ },
122
+ };
123
+
124
+ // W uzyciu
125
+ const error = await FeatureService.create(formData, true);
126
+ if (error instanceof AxiosError && error.response?.status === 422) {
127
+ validationErrors.value = error.response.data.errors;
128
+ }
129
+ ```
130
+
131
+ ## Integracja z Pinia Store
132
+
133
+ ```typescript
134
+ // stores/feature.store.ts
135
+ export const useFeatureStore = defineStore('feature', () => {
136
+ const items = ref<IFeature[]>([]);
137
+ const loading = ref(false);
138
+
139
+ async function fetchAll() {
140
+ loading.value = true;
141
+ try {
142
+ const { data } = await FeatureService.getAll();
143
+ items.value = data;
144
+ } catch (error) {
145
+ printError(error);
146
+ } finally {
147
+ loading.value = false;
148
+ }
149
+ }
150
+
151
+ return { items, loading, fetchAll };
152
+ });
153
+ ```
154
+
155
+ ## Zasady
156
+
157
+ 1. **Jeden serwis per modul** - plik `ModuleName.service.ts`
158
+ 2. **Functional pattern** - obiekt z metodami, nie klasa
159
+ 3. **Typowanie** - kazda metoda typuje request i response
160
+ 4. **Nie wywoluj API bezposrednio z komponentu** - zawsze przez serwis
161
+ 5. **Error handling** - interceptor obsluguje globalne bledy, serwis moze dodac `returnErrors`
162
+ 6. **Zmienne srodowiskowe** - `VITE_API_URL`, `VITE_API_TIMEOUT` w `.env`
163
+ 7. **Brak console.log** - uzyj `printError()` dla bledow
@@ -0,0 +1,160 @@
1
+ ---
2
+ applyTo: "**/*.vue,**/*.ts,**/*.scss"
3
+ ---
4
+
5
+ # Coding Conventions - Instrukcje
6
+
7
+ ## Nazewnictwo
8
+
9
+ ### Pliki i katalogi
10
+
11
+ | Element | Konwencja | Przyklad |
12
+ |---------|-----------|----------|
13
+ | Komponenty Vue | PascalCase | `UserProfile.vue` |
14
+ | Composables | camelCase z prefixem `use` | `useUserForm.ts` |
15
+ | Serwisy | PascalCase + `.service.ts` | `User.service.ts` |
16
+ | Store | camelCase + `.store.ts` | `user.store.ts` |
17
+ | Typy/Interfejsy | PascalCase + `.types.ts` | `User.types.ts` |
18
+ | Testy | nazwa + `.test.ts` | `UserProfile.test.ts` |
19
+ | Stale | camelCase + `consts.ts` | `userConsts.ts` |
20
+
21
+ ### Zmienne i funkcje
22
+
23
+ ```typescript
24
+ // camelCase dla zmiennych i funkcji
25
+ const userName = ref('');
26
+ const isLoading = ref(false);
27
+ function fetchUserData() { /* ... */ }
28
+
29
+ // PascalCase dla komponentow i klas
30
+ import UserProfile from './UserProfile.vue';
31
+
32
+ // UPPERCASE dla stalych globalnych
33
+ const MAX_RETRY_COUNT = 3;
34
+ const API_TIMEOUT = 30000;
35
+ ```
36
+
37
+ ### TypeScript - prefixy typow
38
+
39
+ ```typescript
40
+ // I - Interface
41
+ interface IUser {
42
+ id: number;
43
+ name: string;
44
+ }
45
+
46
+ // E - Enum
47
+ enum EUserRole {
48
+ ADMIN = 'admin',
49
+ USER = 'user',
50
+ }
51
+
52
+ // T - Type alias
53
+ type TUserFilter = 'active' | 'inactive' | 'all';
54
+ ```
55
+
56
+ ### CSS klasy
57
+
58
+ ```scss
59
+ // Single dash - NIE uzywaj BEM (__) ani double dash (--)
60
+ .user-profile { }
61
+ .user-profile-header { }
62
+ .user-profile-avatar { }
63
+
64
+ // ZLE:
65
+ .user-profile__header { } // BEM
66
+ .user-profile--active { } // BEM modifier
67
+ ```
68
+
69
+ ### data-testid
70
+
71
+ ```vue
72
+ <!-- camelCase, opisowy -->
73
+ <button :data-testid="'userProfileSaveButton'">Zapisz</button>
74
+ <input :data-testid="'userProfileEmailInput'" />
75
+ <div :data-testid="'userProfileCard'" />
76
+ ```
77
+
78
+ ## Komentarze i dokumentacja
79
+
80
+ ### Zasady komentowania
81
+
82
+ 1. **Kod ma byc samodokumentujacy** - nazwy zmiennych i funkcji powinny wyjasnic "co"
83
+ 2. **Komentarze tylko gdy "dlaczego"** - nie opisuj co robi kod, opisz dlaczego tak a nie inaczej
84
+ 3. **JSDoc** - tylko dla publicznego API i composables:
85
+
86
+ ```typescript
87
+ /**
88
+ * Composable do zarzadzania formularzem uzytkownika.
89
+ * Obsluguje walidacje, submit i reset.
90
+ */
91
+ export function useUserForm(userId?: number) {
92
+ // ...
93
+ }
94
+ ```
95
+
96
+ 4. **Zakaz komentarzy HTML** w szablonach Vue (`<!-- -->`)
97
+ 5. **Zakaz komentarzy CSS** w stylach (`/* */`)
98
+
99
+ ## Console
100
+
101
+ ### BEZWZGLEDNY ZAKAZ console.log w kodzie produkcyjnym
102
+
103
+ ```typescript
104
+ // ZLE - nigdy nie commituj console.log
105
+ console.log('user:', user);
106
+ console.warn('deprecated');
107
+
108
+ // DOBRZE - uzywaj dedykowanej funkcji
109
+ import { printError } from '@/composables/useShowError';
110
+ printError(error);
111
+
112
+ // LUB - jesli potrzebujesz debug info, usun przed commitem
113
+ ```
114
+
115
+ ## Struktura komponentu Vue
116
+
117
+ Kolejnosc sekcji w pliku `.vue`:
118
+
119
+ ```vue
120
+ <template>
121
+ <!-- 1. Template -->
122
+ </template>
123
+
124
+ <script setup lang="ts">
125
+ // 2. Script setup
126
+ // Kolejnosc wewnatrz:
127
+ // a) importy
128
+ // b) interface props/emits
129
+ // c) defineProps / defineEmits
130
+ // d) inject / provide
131
+ // e) ref / reactive
132
+ // f) computed
133
+ // g) watch
134
+ // h) metody
135
+ // i) lifecycle hooks (onMounted, onUnmounted)
136
+ // j) defineExpose (jesli potrzebny)
137
+ </script>
138
+
139
+ <style scoped lang="scss">
140
+ // 3. Scoped styles
141
+ </style>
142
+ ```
143
+
144
+ ## Code Review Checklist
145
+
146
+ Przed kazdym commitem zweryfikuj:
147
+
148
+ 1. [ ] **TypeScript** - brak `any`, poprawne typy, interfejsy z prefixem `I`
149
+ 2. [ ] **Nazewnictwo** - camelCase zmienne, PascalCase komponenty, single-dash CSS
150
+ 3. [ ] **Console** - brak `console.log/warn/error` (uzyj `printError()`)
151
+ 4. [ ] **Komentarze** - brak zbednych, brak HTML/CSS komentarzy
152
+ 5. [ ] **data-testid** - camelCase, dodane do interaktywnych elementow
153
+ 6. [ ] **Props** - typowane przez interface, domyslne wartosci przez `withDefaults`
154
+ 7. [ ] **Emits** - typowane przez interface
155
+ 8. [ ] **Store** - plik `.store.ts`, `defineStore` z composition syntax
156
+ 9. [ ] **API** - przez serwis, nie bezposrednio z komponentu
157
+ 10. [ ] **Error handling** - `printError()`, nie `console.error()`
158
+ 11. [ ] **Cleanup** - `onUnmounted` czysci listenery, timery, subskrypcje
159
+ 12. [ ] **Testy** - nowy kod ma testy, istniejace testy przechodza
160
+ 13. [ ] **Style** - `scoped`, brak `font-family` (poza monospace), single-dash klasy
@@ -0,0 +1,218 @@
1
+ ---
2
+ applyTo: "**/composables/**,**/use*.ts"
3
+ ---
4
+
5
+ # Composables - Instrukcje
6
+
7
+ ## Konwencje
8
+
9
+ - Nazwa pliku: `use[Feature].ts` (camelCase z prefixem `use`)
10
+ - Lokalizacja: `{{MODULE_PATH}}composables/` lub `/src/composables/` (globalne)
11
+ - Eksport: named function `export function use[Feature]()`
12
+ - Zwraca obiekt z reaktywnymi wartosciami i metodami
13
+
14
+ ## Wzorzec useAsyncDataLoader
15
+
16
+ Uniwersalny composable do operacji asynchronicznych:
17
+
18
+ ```typescript
19
+ // composables/useAsyncDataLoader.ts
20
+ import { ref, type Ref } from 'vue';
21
+
22
+ interface IAsyncDataLoaderOptions<T> {
23
+ onSuccess?: (data: T) => void;
24
+ onError?: (error: unknown) => void;
25
+ throwOnError?: boolean;
26
+ }
27
+
28
+ export function useAsyncDataLoader<T>(
29
+ fetchFn: () => Promise<T>,
30
+ options?: IAsyncDataLoaderOptions<T>,
31
+ ) {
32
+ const loading = ref(false);
33
+ const data: Ref<T | null> = ref(null);
34
+ const error: Ref<Error | null> = ref(null);
35
+
36
+ async function execute() {
37
+ loading.value = true;
38
+ error.value = null;
39
+ try {
40
+ const result = await fetchFn();
41
+ data.value = result;
42
+ options?.onSuccess?.(result);
43
+ } catch (err) {
44
+ error.value = err instanceof Error ? err : new Error(String(err));
45
+ options?.onError?.(err);
46
+ if (options?.throwOnError) throw err;
47
+ } finally {
48
+ loading.value = false;
49
+ }
50
+ }
51
+
52
+ function reset() {
53
+ data.value = null;
54
+ loading.value = false;
55
+ error.value = null;
56
+ }
57
+
58
+ return { loading, data, error, execute, reset };
59
+ }
60
+ ```
61
+
62
+ ### Uzycie
63
+
64
+ ```typescript
65
+ // W composable modulu
66
+ export function useUsers() {
67
+ const { loading, data, execute } = useAsyncDataLoader(
68
+ () => UserService.getAll(),
69
+ {
70
+ onSuccess: (users) => store.setUsers(users),
71
+ onError: (err) => printError(err),
72
+ },
73
+ );
74
+
75
+ return { loading, users: data, fetchUsers: execute };
76
+ }
77
+
78
+ // W komponencie
79
+ const { loading, users, fetchUsers } = useUsers();
80
+ onMounted(() => fetchUsers());
81
+ ```
82
+
83
+ ## Wzorzec composable z formularzem
84
+
85
+ ```typescript
86
+ // composables/useFeatureForm.ts
87
+ export function useFeatureForm(itemId?: number) {
88
+ const form = reactive<IFeatureForm>({
89
+ name: '',
90
+ description: '',
91
+ status: 'draft',
92
+ });
93
+
94
+ const errors = ref<Record<string, string[]>>({});
95
+ const submitting = ref(false);
96
+
97
+ function resetForm() {
98
+ Object.assign(form, { name: '', description: '', status: 'draft' });
99
+ errors.value = {};
100
+ }
101
+
102
+ async function loadItem() {
103
+ if (!itemId) return;
104
+ const { data } = await FeatureService.getById(itemId);
105
+ Object.assign(form, data);
106
+ }
107
+
108
+ async function submitForm() {
109
+ submitting.value = true;
110
+ errors.value = {};
111
+ try {
112
+ if (itemId) {
113
+ await FeatureService.update(itemId, form);
114
+ } else {
115
+ await FeatureService.create(form);
116
+ }
117
+ return true;
118
+ } catch (error) {
119
+ if (error instanceof AxiosError && error.response?.status === 422) {
120
+ errors.value = error.response.data.errors;
121
+ }
122
+ return false;
123
+ } finally {
124
+ submitting.value = false;
125
+ }
126
+ }
127
+
128
+ return { form, errors, submitting, resetForm, loadItem, submitForm };
129
+ }
130
+ ```
131
+
132
+ ## Wzorzec composable z filtrowaniem
133
+
134
+ ```typescript
135
+ // composables/useFeatureFilters.ts
136
+ export function useFeatureFilters(items: Ref<IFeature[]>) {
137
+ const searchQuery = ref('');
138
+ const statusFilter = ref<TStatus | null>(null);
139
+ const sortBy = ref<'name' | 'date'>('date');
140
+ const sortOrder = ref<'asc' | 'desc'>('desc');
141
+
142
+ const filtered = computed(() => {
143
+ let result = [...items.value];
144
+
145
+ if (searchQuery.value) {
146
+ const q = searchQuery.value.toLowerCase();
147
+ result = result.filter(item =>
148
+ item.name.toLowerCase().includes(q),
149
+ );
150
+ }
151
+
152
+ if (statusFilter.value) {
153
+ result = result.filter(item => item.status === statusFilter.value);
154
+ }
155
+
156
+ result.sort((a, b) => {
157
+ const modifier = sortOrder.value === 'asc' ? 1 : -1;
158
+ return a[sortBy.value] > b[sortBy.value] ? modifier : -modifier;
159
+ });
160
+
161
+ return result;
162
+ });
163
+
164
+ return { searchQuery, statusFilter, sortBy, sortOrder, filtered };
165
+ }
166
+ ```
167
+
168
+ ## Wzorzec composable z localStorage
169
+
170
+ ```typescript
171
+ // composables/useLocalStorage.ts
172
+ export function useLocalStorage<T>(key: string, defaultValue: T) {
173
+ const stored = localStorage.getItem(key);
174
+ const data = ref<T>(stored ? JSON.parse(stored) : defaultValue) as Ref<T>;
175
+
176
+ watch(data, (newValue) => {
177
+ localStorage.setItem(key, JSON.stringify(newValue));
178
+ }, { deep: true });
179
+
180
+ function remove() {
181
+ localStorage.removeItem(key);
182
+ data.value = defaultValue;
183
+ }
184
+
185
+ return { data, remove };
186
+ }
187
+ ```
188
+
189
+ ## Wzorzec composable z window events
190
+
191
+ ```typescript
192
+ // composables/useWindowSize.ts
193
+ export function useWindowSize() {
194
+ const width = ref(window.innerWidth);
195
+ const height = ref(window.innerHeight);
196
+
197
+ function onResize() {
198
+ width.value = window.innerWidth;
199
+ height.value = window.innerHeight;
200
+ }
201
+
202
+ onMounted(() => window.addEventListener('resize', onResize));
203
+ onUnmounted(() => window.removeEventListener('resize', onResize));
204
+
205
+ return { width, height };
206
+ }
207
+ ```
208
+
209
+ ## Zasady
210
+
211
+ 1. **Zawsze czyszcz zasoby** - `onUnmounted` dla listenerow, timerow, subskrypcji
212
+ 2. **Zwracaj reaktywne wartosci** - `ref`, `computed`, nie surowe wartosci
213
+ 3. **Nie uzywaj `this`** - composables to funkcje, nie klasy
214
+ 4. **Jeden composable = jedna odpowiedzialnosc** - nie mieszaj filtrowania z API calls
215
+ 5. **Typuj parametry i return** - TypeScript musi wiedziec co zwracasz
216
+ 6. **Nie wywoluj side-effectow w konstruktorze** - uzytkownik composable decyduje kiedy `execute()`
217
+ 7. **`printError()` zamiast `console.error()`** - dla obslugi bledow
218
+ 8. **Testowalnosc** - composable powinien byc latwy do przetestowania w izolacji