vibes-plug 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.
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/BLUEPRINT.md +125 -0
- package/CHANGELOG.md +195 -0
- package/CONTRIBUTING.md +199 -0
- package/LICENSE +21 -0
- package/README.md +263 -0
- package/SECURITY.md +21 -0
- package/banner.png +0 -0
- package/package.json +25 -0
- package/plugin.json +8 -0
- package/scripts/update_skills.js +75 -0
- package/skills/ai-llm-integration-expert/SKILL.md +162 -0
- package/skills/api-design-expert/SKILL.md +310 -0
- package/skills/app-analyzer-optimizer/SKILL.md +189 -0
- package/skills/asisten_ramah/SKILL.md +41 -0
- package/skills/authentication-identity-expert/SKILL.md +45 -0
- package/skills/auto-doc-updater/SKILL.md +204 -0
- package/skills/bootstrap-to-modern/SKILL.md +87 -0
- package/skills/brainstorming/SKILL.md +353 -0
- package/skills/bun-runtime-expert/SKILL.md +211 -0
- package/skills/ci-cd-devops-architect/SKILL.md +45 -0
- package/skills/cloud-hosting-expert/SKILL.md +244 -0
- package/skills/coderabbit/SKILL.md +192 -0
- package/skills/data-telemetry-expert/SKILL.md +213 -0
- package/skills/database-orm-expert/SKILL.md +294 -0
- package/skills/design-system-architect/SKILL.md +243 -0
- package/skills/e2e-testing-expert/SKILL.md +315 -0
- package/skills/event-driven-architect/SKILL.md +81 -0
- package/skills/firebase-security-expert/SKILL.md +195 -0
- package/skills/fullstack-expert/SKILL.md +202 -0
- package/skills/fullstack-expert/references/api_design_guide.md +466 -0
- package/skills/fullstack-expert/references/devops_infrastructure.md +477 -0
- package/skills/fullstack-expert/references/multi_language_backend.md +528 -0
- package/skills/fullstack-expert/references/system_design_patterns.md +358 -0
- package/skills/fullstack-expert/scripts/api_contract_validator.py +253 -0
- package/skills/fullstack-expert/scripts/architecture_analyzer.py +326 -0
- package/skills/gemini-agent-booster/SKILL.md +135 -0
- package/skills/global-a11y-i18n-expert/SKILL.md +81 -0
- package/skills/go-programming-expert/SKILL.md +295 -0
- package/skills/hig/SKILL.md +188 -0
- package/skills/js-backend-expert/SKILL.md +192 -0
- package/skills/mcp-server-architect/SKILL.md +194 -0
- package/skills/mobile-expo-expert/SKILL.md +186 -0
- package/skills/monday-design-aesthetic/SKILL.md +67 -0
- package/skills/monorepo-architect/SKILL.md +227 -0
- package/skills/mpa-orchestrator/SKILL.md +101 -0
- package/skills/multi-agent-orchestration/SKILL.md +234 -0
- package/skills/multiple-entry-points/SKILL.md +55 -0
- package/skills/mvc-expert/SKILL.md +231 -0
- package/skills/payment-gateway-expert/SKILL.md +45 -0
- package/skills/performance-web-vitals/SKILL.md +332 -0
- package/skills/prd-architect/SKILL.md +191 -0
- package/skills/production-ready-hardener/SKILL.md +469 -0
- package/skills/production-ready-hardener/references/performance_optimization.md +441 -0
- package/skills/production-ready-hardener/references/production_checklist.md +161 -0
- package/skills/production-ready-hardener/references/security_hardening_guide.md +379 -0
- package/skills/production-ready-hardener/scripts/production_readiness_scanner.py +875 -0
- package/skills/python-programming-expert/SKILL.md +271 -0
- package/skills/realtime-collaboration-expert/SKILL.md +45 -0
- package/skills/rust-programming-expert/SKILL.md +235 -0
- package/skills/saas-billing/SKILL.md +377 -0
- package/skills/saas-multi-tenant/SKILL.md +237 -0
- package/skills/saas-mvp-launcher/SKILL.md +231 -0
- package/skills/saas-transformer/SKILL.md +446 -0
- package/skills/saas-transformer/references/billing_integration_guide.md +401 -0
- package/skills/saas-transformer/references/feature_gating_patterns.md +137 -0
- package/skills/saas-transformer/references/saas_transformation_checklist.md +121 -0
- package/skills/saas-transformer/scripts/saas_transformation_scanner.py +254 -0
- package/skills/scalability-clean-code/SKILL.md +229 -0
- package/skills/secure-fuzz-testing/SKILL.md +201 -0
- package/skills/senior-frontend/SKILL.md +161 -0
- package/skills/senior-frontend/references/frontend_best_practices.md +806 -0
- package/skills/senior-frontend/references/nextjs_optimization_guide.md +724 -0
- package/skills/senior-frontend/references/react_patterns.md +746 -0
- package/skills/senior-frontend/scripts/bundle_analyzer.py +407 -0
- package/skills/senior-frontend/scripts/component_generator.py +329 -0
- package/skills/senior-frontend/scripts/frontend_scaffolder.py +1005 -0
- package/skills/senior-fullstack/SKILL.md +167 -0
- package/skills/senior-fullstack/references/architecture_patterns.md +160 -0
- package/skills/senior-fullstack/references/development_workflows.md +222 -0
- package/skills/senior-fullstack/references/tech_stack_guide.md +190 -0
- package/skills/senior-fullstack/scripts/code_quality_analyzer.py +114 -0
- package/skills/senior-fullstack/scripts/fullstack_scaffolder.py +114 -0
- package/skills/senior-fullstack/scripts/project_scaffolder.py +114 -0
- package/skills/seo/SKILL.md +225 -0
- package/skills/seo/references/cwv-thresholds.md +108 -0
- package/skills/seo/references/eeat-framework.md +214 -0
- package/skills/seo/references/quality-gates.md +155 -0
- package/skills/seo/references/schema-types.md +118 -0
- package/skills/seo-aeo-landing-page-writer/SKILL.md +97 -0
- package/skills/seo-geo/SKILL.md +188 -0
- package/skills/session-handoff-resume/SKILL.md +158 -0
- package/skills/skill_baru/SKILL.md +147 -0
- package/skills/spa-orchestrator/SKILL.md +288 -0
- package/skills/state-management-expert/SKILL.md +272 -0
- package/skills/supabase-migration/SKILL.md +45 -0
- package/skills/supabase-security-expert/SKILL.md +243 -0
- package/skills/tailwind-expert/SKILL.md +188 -0
- package/skills/tanstack-query-expert/SKILL.md +199 -0
- package/skills/tauri-expert/SKILL.md +97 -0
- package/skills/token-saver/SKILL.md +111 -0
- package/skills/typescript-expert/SKILL.md +279 -0
- package/skills/ui-components-expert/SKILL.md +63 -0
- package/skills/ui-ux-pro-max/SKILL.md +201 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-310.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-310.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/core.py +257 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +493 -0
- package/skills/ui-ux-pro-max/scripts/search.py +81 -0
- package/skills/ui_ux_expert/SKILL.md +114 -0
- package/skills/vibe-code-gardener/SKILL.md +173 -0
- package/skills/web-scraper/SKILL.md +205 -0
- package/skills/web-scraper/references/data-transforms.md +397 -0
- package/skills/web-scraper/references/extraction-patterns.md +475 -0
- package/skills/web-scraper/references/output-templates.md +481 -0
- package/skills/zero-to-prod-orchestrator/SKILL.md +180 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: state-management-expert
|
|
3
|
+
description: "Expert guide for modern client-side state management: Zustand, Jotai, Valtio, TanStack Store, Redux Toolkit, and server state patterns with TanStack Query / Panduan ahli untuk manajemen state client-side modern: Zustand, Jotai, Valtio, TanStack Store, Redux Toolkit, dan pola server state dengan TanStack Query."
|
|
4
|
+
author: "Roedy Rustam"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# State Management Expert (Modern React Edition)
|
|
8
|
+
|
|
9
|
+
[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<a name="english"></a>
|
|
14
|
+
## English
|
|
15
|
+
|
|
16
|
+
### Description
|
|
17
|
+
Expert guide for selecting and implementing the right state management solution for React and Next.js applications. Covers **Zustand 5**, **Jotai 2**, **Valtio**, **TanStack Store**, and **Redux Toolkit 2**, with clear guidance on when to use each, proper separation of server state (TanStack Query) from client state, and advanced patterns like slices, persistence, and devtools integration.
|
|
18
|
+
|
|
19
|
+
### Trigger Conditions
|
|
20
|
+
- Choosing a state management library for a new React/Next.js project.
|
|
21
|
+
- Refactoring prop drilling or complex Context API usage.
|
|
22
|
+
- Implementing global UI state (modals, drawers, theme, auth session).
|
|
23
|
+
- Managing complex form state across multiple steps.
|
|
24
|
+
- Implementing undo/redo, optimistic updates, or derived state.
|
|
25
|
+
- Migrating from Redux to a lighter alternative.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### State Category Separation (Critical First Step)
|
|
30
|
+
|
|
31
|
+
Before choosing a library, categorize your state:
|
|
32
|
+
|
|
33
|
+
| Category | Description | Solution |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| **Server State** | Data from APIs, databases | TanStack Query / SWR |
|
|
36
|
+
| **URL State** | Route params, search filters | `useSearchParams`, nuqs |
|
|
37
|
+
| **Form State** | Input values, validation errors | React Hook Form + Zod |
|
|
38
|
+
| **UI/Local State** | Modals, drawers, toggles | `useState` / `useReducer` |
|
|
39
|
+
| **Global Client State** | Theme, auth, shopping cart | Zustand / Jotai |
|
|
40
|
+
| **Cross-tab State** | Sync across browser tabs | Zustand + BroadcastChannel |
|
|
41
|
+
|
|
42
|
+
**Rule**: Do NOT store server data in client state. Let TanStack Query own all async data.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### Library Selection Guide
|
|
47
|
+
|
|
48
|
+
| Criteria | Zustand 5 | Jotai 2 | Valtio | Redux Toolkit |
|
|
49
|
+
|---|---|---|---|---|
|
|
50
|
+
| **Mental Model** | Single store, actions | Atomic, bottom-up | Proxy-based, mutable | Flux, reducers |
|
|
51
|
+
| **Bundle Size** | ~1KB | ~3KB | ~3KB | ~15KB+ |
|
|
52
|
+
| **DevTools** | Redux DevTools | Jotai DevTools | Redux DevTools | Built-in |
|
|
53
|
+
| **Async** | Manual or middleware | `atomWithQuery` | `snapshot` | `createAsyncThunk` |
|
|
54
|
+
| **Best For** | General global state | Atomic derived state | Simple mutable state | Large teams, strict patterns |
|
|
55
|
+
|
|
56
|
+
**Recommendation**: Use **Zustand** as the default. Use **Jotai** for fine-grained reactive atoms. Use **Redux Toolkit** only for large enterprise teams with strict conventions.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### Zustand 5 — Best Practices
|
|
61
|
+
|
|
62
|
+
#### Basic Store with TypeScript
|
|
63
|
+
```typescript
|
|
64
|
+
// stores/ui.store.ts
|
|
65
|
+
import { create } from 'zustand';
|
|
66
|
+
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
|
|
67
|
+
import { immer } from 'zustand/middleware/immer';
|
|
68
|
+
|
|
69
|
+
interface UiState {
|
|
70
|
+
theme: 'light' | 'dark' | 'system';
|
|
71
|
+
sidebarOpen: boolean;
|
|
72
|
+
activeModal: string | null;
|
|
73
|
+
setTheme: (theme: UiState['theme']) => void;
|
|
74
|
+
toggleSidebar: () => void;
|
|
75
|
+
openModal: (modalId: string) => void;
|
|
76
|
+
closeModal: () => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const useUiStore = create<UiState>()(
|
|
80
|
+
devtools(
|
|
81
|
+
persist(
|
|
82
|
+
subscribeWithSelector(
|
|
83
|
+
immer((set) => ({
|
|
84
|
+
theme: 'system',
|
|
85
|
+
sidebarOpen: true,
|
|
86
|
+
activeModal: null,
|
|
87
|
+
setTheme: (theme) => set((state) => { state.theme = theme; }),
|
|
88
|
+
toggleSidebar: () => set((state) => { state.sidebarOpen = !state.sidebarOpen; }),
|
|
89
|
+
openModal: (modalId) => set((state) => { state.activeModal = modalId; }),
|
|
90
|
+
closeModal: () => set((state) => { state.activeModal = null; }),
|
|
91
|
+
}))
|
|
92
|
+
),
|
|
93
|
+
{ name: 'ui-store', partialize: (s) => ({ theme: s.theme }) }
|
|
94
|
+
),
|
|
95
|
+
{ name: 'UiStore' }
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### Slice Pattern for Large Stores
|
|
101
|
+
```typescript
|
|
102
|
+
// Pattern: separate slices, combine into one store
|
|
103
|
+
import { StateCreator } from 'zustand';
|
|
104
|
+
|
|
105
|
+
interface AuthSlice {
|
|
106
|
+
user: User | null;
|
|
107
|
+
isAuthenticated: boolean;
|
|
108
|
+
login: (user: User) => void;
|
|
109
|
+
logout: () => void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface CartSlice {
|
|
113
|
+
items: CartItem[];
|
|
114
|
+
addItem: (item: CartItem) => void;
|
|
115
|
+
removeItem: (id: string) => void;
|
|
116
|
+
total: () => number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
type AppStore = AuthSlice & CartSlice;
|
|
120
|
+
|
|
121
|
+
const createAuthSlice: StateCreator<AppStore, [], [], AuthSlice> = (set) => ({
|
|
122
|
+
user: null,
|
|
123
|
+
isAuthenticated: false,
|
|
124
|
+
login: (user) => set({ user, isAuthenticated: true }),
|
|
125
|
+
logout: () => set({ user: null, isAuthenticated: false }),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const createCartSlice: StateCreator<AppStore, [], [], CartSlice> = (set, get) => ({
|
|
129
|
+
items: [],
|
|
130
|
+
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
|
|
131
|
+
removeItem: (id) => set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
|
|
132
|
+
total: () => get().items.reduce((sum, item) => sum + item.price * item.qty, 0),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export const useAppStore = create<AppStore>()(
|
|
136
|
+
devtools((...args) => ({
|
|
137
|
+
...createAuthSlice(...args),
|
|
138
|
+
...createCartSlice(...args),
|
|
139
|
+
}))
|
|
140
|
+
);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Selective Subscriptions (Prevent Unnecessary Rerenders)
|
|
144
|
+
```typescript
|
|
145
|
+
// Only re-render when `theme` changes, not the entire store
|
|
146
|
+
const theme = useUiStore((s) => s.theme);
|
|
147
|
+
const isModalOpen = useUiStore((s) => s.activeModal === 'confirm-delete');
|
|
148
|
+
|
|
149
|
+
// Subscribe outside React (side effects, non-component code)
|
|
150
|
+
useUiStore.subscribe(
|
|
151
|
+
(s) => s.theme,
|
|
152
|
+
(theme) => document.documentElement.dataset.theme = theme,
|
|
153
|
+
{ fireImmediately: true }
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### Jotai 2 — Atomic State Patterns
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// atoms/user.atoms.ts
|
|
163
|
+
import { atom, loadable } from 'jotai';
|
|
164
|
+
import { atomWithStorage, atomWithReset } from 'jotai/utils';
|
|
165
|
+
|
|
166
|
+
// Primitive atom
|
|
167
|
+
export const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light');
|
|
168
|
+
|
|
169
|
+
// Derived (read-only) atom
|
|
170
|
+
export const isDarkAtom = atom((get) => get(themeAtom) === 'dark');
|
|
171
|
+
|
|
172
|
+
// Async atom with loadable for Suspense-free usage
|
|
173
|
+
export const userAtom = atom(async () => {
|
|
174
|
+
const res = await fetch('/api/me');
|
|
175
|
+
if (!res.ok) throw new Error('Failed to fetch user');
|
|
176
|
+
return res.json() as Promise<User>;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
export const loadableUserAtom = loadable(userAtom);
|
|
180
|
+
|
|
181
|
+
// In component:
|
|
182
|
+
function UserWidget() {
|
|
183
|
+
const loadableUser = useAtomValue(loadableUserAtom);
|
|
184
|
+
if (loadableUser.state === 'loading') return <Spinner />;
|
|
185
|
+
if (loadableUser.state === 'hasError') return <Error />;
|
|
186
|
+
return <div>{loadableUser.data.name}</div>;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### Redux Toolkit 2 — Modern Patterns
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// features/cart/cartSlice.ts
|
|
196
|
+
import { createSlice, PayloadAction, createSelector } from '@reduxjs/toolkit';
|
|
197
|
+
|
|
198
|
+
interface CartState {
|
|
199
|
+
items: CartItem[];
|
|
200
|
+
status: 'idle' | 'loading' | 'failed';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const cartSlice = createSlice({
|
|
204
|
+
name: 'cart',
|
|
205
|
+
initialState: { items: [], status: 'idle' } as CartState,
|
|
206
|
+
reducers: {
|
|
207
|
+
addItem(state, action: PayloadAction<CartItem>) {
|
|
208
|
+
state.items.push(action.payload); // Immer handles immutability
|
|
209
|
+
},
|
|
210
|
+
removeItem(state, action: PayloadAction<string>) {
|
|
211
|
+
state.items = state.items.filter((i) => i.id !== action.payload);
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Memoized selector
|
|
217
|
+
export const selectCartTotal = createSelector(
|
|
218
|
+
(state: RootState) => state.cart.items,
|
|
219
|
+
(items) => items.reduce((sum, i) => sum + i.price * i.qty, 0)
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
export const { addItem, removeItem } = cartSlice.actions;
|
|
223
|
+
export default cartSlice.reducer;
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
### Integration with Next.js App Router
|
|
229
|
+
|
|
230
|
+
- **Server Components**: No client state. Fetch directly.
|
|
231
|
+
- **Client Components**: Use Zustand/Jotai wrapped in providers.
|
|
232
|
+
- **Hydration**: Initialize Zustand store from server-fetched data using `useHydrateAtoms` (Jotai) or a `HydrationBoundary` pattern.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// providers/store-provider.tsx (App Router pattern)
|
|
236
|
+
'use client';
|
|
237
|
+
import { createStore, Provider } from 'jotai';
|
|
238
|
+
import { useRef } from 'react';
|
|
239
|
+
|
|
240
|
+
type Props = { children: React.ReactNode; initialUser?: User };
|
|
241
|
+
|
|
242
|
+
export function StoreProvider({ children, initialUser }: Props) {
|
|
243
|
+
const store = useRef(createStore());
|
|
244
|
+
if (initialUser) store.current.set(userAtom, initialUser);
|
|
245
|
+
return <Provider store={store.current}>{children}</Provider>;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
<a name="bahasa-indonesia"></a>
|
|
252
|
+
## Bahasa Indonesia
|
|
253
|
+
|
|
254
|
+
### Deskripsi
|
|
255
|
+
Panduan ahli untuk memilih dan mengimplementasikan solusi manajemen state yang tepat untuk aplikasi React dan Next.js. Mencakup **Zustand 5**, **Jotai 2**, **Valtio**, dan **Redux Toolkit 2**, dengan panduan pemilihan yang jelas, pemisahan server state (TanStack Query) dari client state, dan pola lanjutan seperti slices, persistensi, dan integrasi devtools.
|
|
256
|
+
|
|
257
|
+
### Kondisi Pemicu
|
|
258
|
+
- Memilih library state management untuk proyek React/Next.js baru.
|
|
259
|
+
- Refaktor prop drilling atau penggunaan Context API yang rumit.
|
|
260
|
+
- Mengimplementasikan global UI state (modal, drawer, tema, sesi auth).
|
|
261
|
+
- Mengelola state form kompleks di beberapa langkah.
|
|
262
|
+
- Mengimplementasikan undo/redo, optimistic update, atau derived state.
|
|
263
|
+
- Migrasi dari Redux ke alternatif yang lebih ringan.
|
|
264
|
+
|
|
265
|
+
### Prinsip Utama
|
|
266
|
+
|
|
267
|
+
- **Pisahkan server state dari client state**: Server state (data dari API) dikelola TanStack Query. Client state (UI, preferensi) dikelola Zustand/Jotai.
|
|
268
|
+
- **Gunakan Zustand sebagai default** untuk state global umum.
|
|
269
|
+
- **Gunakan Jotai** untuk state atomik yang sangat granular dan reaktif.
|
|
270
|
+
- **Hindari menyimpan data server di client state** — ini adalah sumber bug terbesar di aplikasi React.
|
|
271
|
+
- **Selective subscription**: Selalu gunakan selector untuk menghindari re-render yang tidak perlu.
|
|
272
|
+
- **DevTools**: Selalu integrasikan devtools di development untuk debugging yang efektif.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supabase-migration
|
|
3
|
+
description: "A skill to create or apply a Supabase database migration / Kemampuan untuk membuat atau menerapkan migrasi database Supabase."
|
|
4
|
+
author: "Roedy Rustam"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Supabase Migration Skill
|
|
8
|
+
|
|
9
|
+
[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<a name="english"></a>
|
|
14
|
+
## English
|
|
15
|
+
|
|
16
|
+
### Description
|
|
17
|
+
When the user asks to create or apply a database migration for Supabase, follow these instructions:
|
|
18
|
+
|
|
19
|
+
### Instructions
|
|
20
|
+
1. **Check Environment**: Ensure `supabase/migrations` directory exists. Run `list_dir` on it if needed.
|
|
21
|
+
2. **Review Available MCP Commands**: Utilize the Supabase MCP integration.
|
|
22
|
+
- For listing existing migrations, use `mcp_supabase-mcp-server_list_migrations` with `project_id`.
|
|
23
|
+
- Before executing migration queries on the server directly, confirm the SQL is correct.
|
|
24
|
+
3. **Execute Command**:
|
|
25
|
+
- Write the SQL script to a local migration file located in `supabase/migrations/` using `write_to_file`. Use standard timestamps or `supabase migration new` command to get the filename.
|
|
26
|
+
- Run `npx supabase migration up` to test locally or use the remote MCP `apply_migration` if the user wants it applied remotely.
|
|
27
|
+
4. **Final Step**: Confirm with the user that the migration successfully completed.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<a name="bahasa-indonesia"></a>
|
|
32
|
+
## Bahasa Indonesia
|
|
33
|
+
|
|
34
|
+
### Deskripsi
|
|
35
|
+
Ketika pengguna meminta untuk membuat atau menerapkan migrasi database untuk Supabase, ikuti instruksi berikut:
|
|
36
|
+
|
|
37
|
+
### Instruksi
|
|
38
|
+
1. **Periksa Lingkungan**: Pastikan direktori `supabase/migrations` ada. Jalankan `list_dir` jika diperlukan.
|
|
39
|
+
2. **Tinjau Perintah MCP yang Tersedia**: Manfaatkan integrasi Supabase MCP.
|
|
40
|
+
- Untuk mencantumkan migrasi yang ada, gunakan `mcp_supabase-mcp-server_list_migrations` dengan `project_id`.
|
|
41
|
+
- Sebelum menjalankan kueri migrasi di server secara langsung, pastikan kode SQL-nya sudah benar.
|
|
42
|
+
3. **Jalankan Perintah**:
|
|
43
|
+
- Tulis skrip SQL ke file migrasi lokal yang terletak di `supabase/migrations/` menggunakan `write_to_file`. Gunakan timestamp standar atau perintah `supabase migration new` untuk mendapatkan nama file.
|
|
44
|
+
- Jalankan `npx supabase migration up` untuk menguji secara lokal atau gunakan MCP remote `apply_migration` jika pengguna ingin menerapkannya secara remote.
|
|
45
|
+
4. **Langkah Terakhir**: Konfirmasikan dengan pengguna bahwa migrasi telah berhasil diselesaikan.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supabase-security-expert
|
|
3
|
+
description: "Supabase security expert to audit RLS (Row Level Security), RBAC, relational databases, prevent data leakage, and utilize Supabase Linter / Ahli keamanan Supabase untuk audit RLS (Row Level Security), RBAC, database relasional, pencegahan kebocoran data, dan pemanfaatan Supabase Linter."
|
|
4
|
+
author: "Roedy Rustam"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Supabase Security Expert (2026 Edition — Auth v3 / PKCE)
|
|
8
|
+
|
|
9
|
+
[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<a name="english"></a>
|
|
14
|
+
## English
|
|
15
|
+
|
|
16
|
+
### Description
|
|
17
|
+
Expert guide for auditing and hardening Supabase applications. Covers Row Level Security (RLS), Supabase Auth v3 (PKCE flow), API key hygiene, data leakage prevention, Supabase Linter, and production security checklists.
|
|
18
|
+
|
|
19
|
+
### Trigger Conditions
|
|
20
|
+
- Auditing RLS policies for correctness and security gaps.
|
|
21
|
+
- Setting up Supabase Auth v3 with PKCE flow (replaces implicit flow).
|
|
22
|
+
- Reviewing API key usage (anon key vs. service role key).
|
|
23
|
+
- Preventing data leakage from misconfigured policies.
|
|
24
|
+
- Running Supabase Linter (`supabase lint`) for automated security checks.
|
|
25
|
+
- Implementing user roles and access control in a Supabase project.
|
|
26
|
+
|
|
27
|
+
### Supabase Auth v3 — PKCE Flow (2026 Default)
|
|
28
|
+
|
|
29
|
+
Supabase Auth v3 now uses **PKCE (Proof Key for Code Exchange)** as the default flow for all OAuth and magic link authentication — replacing the older implicit flow:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// supabase/client.ts — v3 client setup
|
|
33
|
+
import { createBrowserClient } from '@supabase/ssr';
|
|
34
|
+
|
|
35
|
+
export const supabase = createBrowserClient(
|
|
36
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
37
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
38
|
+
{
|
|
39
|
+
auth: {
|
|
40
|
+
flowType: 'pkce', // Default in Auth v3
|
|
41
|
+
autoRefreshToken: true,
|
|
42
|
+
persistSession: true,
|
|
43
|
+
detectSessionInUrl: true,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Server-side session handling (Next.js App Router)
|
|
51
|
+
import { createServerClient } from '@supabase/ssr';
|
|
52
|
+
import { cookies } from 'next/headers';
|
|
53
|
+
|
|
54
|
+
export async function createSupabaseServerClient() {
|
|
55
|
+
const cookieStore = await cookies();
|
|
56
|
+
return createServerClient(
|
|
57
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
58
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
59
|
+
{
|
|
60
|
+
cookies: {
|
|
61
|
+
getAll: () => cookieStore.getAll(),
|
|
62
|
+
setAll: (cookiesToSet) => {
|
|
63
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
64
|
+
cookieStore.set(name, value, options)
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### API Key Security
|
|
74
|
+
|
|
75
|
+
| Key Type | Purpose | Where to Use |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `anon` key | Public client access — restricted by RLS | Frontend (browser) |
|
|
78
|
+
| `service_role` key | Bypasses ALL RLS | Backend only (server) |
|
|
79
|
+
|
|
80
|
+
> ⚠️ **NEVER** expose `service_role` key to the frontend. If it leaks, an attacker can read/write all data in your database, bypassing RLS completely.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// ✅ CORRECT: service_role used only in server-side code
|
|
84
|
+
// app/api/admin/route.ts (only accessible at admin.domain.com)
|
|
85
|
+
import { createClient } from '@supabase/supabase-js';
|
|
86
|
+
|
|
87
|
+
const adminClient = createClient(
|
|
88
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
89
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY!, // NOT exposed to client
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// 🔴 WRONG: service_role in client-side code
|
|
93
|
+
const publicClient = createBrowserClient(url, process.env.SUPABASE_SERVICE_ROLE_KEY!);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### RLS Policy Audit Checklist
|
|
97
|
+
|
|
98
|
+
#### Common RLS Mistakes
|
|
99
|
+
```sql
|
|
100
|
+
-- 🔴 INSECURE: auth.uid() is not validated — returns null if not authenticated
|
|
101
|
+
CREATE POLICY "users can see own data" ON profiles
|
|
102
|
+
FOR SELECT USING (user_id = auth.uid());
|
|
103
|
+
-- If user is unauthenticated, auth.uid() returns null, which means
|
|
104
|
+
-- NULL = NULL is NULL (falsy in SQL) — so this IS secure... but next one isn't:
|
|
105
|
+
|
|
106
|
+
-- 🔴 DANGEROUS: Using 'true' without auth check
|
|
107
|
+
CREATE POLICY "all can read" ON public_posts
|
|
108
|
+
FOR SELECT USING (true); -- Anyone, including anonymous, can read
|
|
109
|
+
|
|
110
|
+
-- ✅ SECURE: Explicit authentication requirement
|
|
111
|
+
CREATE POLICY "only authenticated users can read" ON sensitive_data
|
|
112
|
+
FOR SELECT USING (auth.role() = 'authenticated');
|
|
113
|
+
|
|
114
|
+
-- ✅ SECURE: Workspace isolation with auth check
|
|
115
|
+
CREATE POLICY "workspace isolation" ON projects
|
|
116
|
+
FOR ALL USING (
|
|
117
|
+
workspace_id IN (
|
|
118
|
+
SELECT workspace_id FROM workspace_members
|
|
119
|
+
WHERE user_id = auth.uid()
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### RLS Audit SQL Queries
|
|
125
|
+
```sql
|
|
126
|
+
-- Find tables WITHOUT RLS enabled
|
|
127
|
+
SELECT schemaname, tablename
|
|
128
|
+
FROM pg_tables
|
|
129
|
+
WHERE schemaname = 'public'
|
|
130
|
+
AND tablename NOT IN (
|
|
131
|
+
SELECT tablename FROM pg_policies WHERE schemaname = 'public'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
-- Find tables with RLS enabled but NO policies (effectively blocks all access)
|
|
135
|
+
SELECT c.relname AS table_name, c.relrowsecurity AS rls_enabled
|
|
136
|
+
FROM pg_class c
|
|
137
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
138
|
+
WHERE n.nspname = 'public'
|
|
139
|
+
AND c.relkind = 'r'
|
|
140
|
+
AND c.relrowsecurity = true
|
|
141
|
+
AND NOT EXISTS (
|
|
142
|
+
SELECT 1 FROM pg_policies p
|
|
143
|
+
WHERE p.tablename = c.relname AND p.schemaname = n.nspname
|
|
144
|
+
);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Supabase Linter
|
|
148
|
+
Run automated security checks:
|
|
149
|
+
```bash
|
|
150
|
+
# Install Supabase CLI
|
|
151
|
+
npm install -g supabase
|
|
152
|
+
|
|
153
|
+
# Run the linter against your project
|
|
154
|
+
supabase db lint --project-id <your-project-id>
|
|
155
|
+
|
|
156
|
+
# Common lint checks:
|
|
157
|
+
# - Tables with RLS disabled
|
|
158
|
+
# - Policies using mutable functions (NOW(), RANDOM())
|
|
159
|
+
# - Security definer functions without search_path
|
|
160
|
+
# - Auth functions used incorrectly
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Storage Security
|
|
164
|
+
```typescript
|
|
165
|
+
// Create a private storage bucket (files not publicly accessible by URL)
|
|
166
|
+
const { data, error } = await supabase.storage.createBucket('user-uploads', {
|
|
167
|
+
public: false, // Private — requires signed URLs
|
|
168
|
+
fileSizeLimit: 10 * 1024 * 1024, // 10MB
|
|
169
|
+
allowedMimeTypes: ['image/jpeg', 'image/png', 'application/pdf'],
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Generate a signed URL (expires in 1 hour)
|
|
173
|
+
const { data: { signedUrl } } = await supabase.storage
|
|
174
|
+
.from('user-uploads')
|
|
175
|
+
.createSignedUrl(`${userId}/${filename}`, 3600);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```sql
|
|
179
|
+
-- Storage RLS: users can only access their own files
|
|
180
|
+
CREATE POLICY "users can manage own files"
|
|
181
|
+
ON storage.objects FOR ALL
|
|
182
|
+
USING (bucket_id = 'user-uploads' AND auth.uid()::text = (storage.foldername(name))[1]);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Production Security Checklist
|
|
186
|
+
- [ ] All tables in `public` schema have RLS enabled.
|
|
187
|
+
- [ ] `service_role` key is only in server-side environment variables.
|
|
188
|
+
- [ ] PKCE flow enabled (`flowType: 'pkce'`) in Auth v3 client.
|
|
189
|
+
- [ ] Storage buckets are private by default.
|
|
190
|
+
- [ ] JWT expiry set appropriately (recommended: 1 hour + refresh tokens).
|
|
191
|
+
- [ ] Email confirmations required for new signups.
|
|
192
|
+
- [ ] Supabase Linter (`supabase db lint`) passes with no critical issues.
|
|
193
|
+
- [ ] MFA (Multi-Factor Authentication) enabled for admin users.
|
|
194
|
+
- [ ] Realtime subscriptions restricted — filter by authenticated user.
|
|
195
|
+
- [ ] Custom `auth.uid()` policies tested with anonymous/different user sessions.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
<a name="bahasa-indonesia"></a>
|
|
200
|
+
## Bahasa Indonesia
|
|
201
|
+
|
|
202
|
+
### Deskripsi
|
|
203
|
+
Panduan ahli untuk mengaudit dan mengeraskan aplikasi Supabase. Mencakup Row Level Security (RLS), Supabase Auth v3 (PKCE flow), kebersihan API key, pencegahan kebocoran data, Supabase Linter, dan checklist keamanan produksi.
|
|
204
|
+
|
|
205
|
+
### Kondisi Pemicu
|
|
206
|
+
- Mengaudit kebijakan RLS untuk kebenaran dan celah keamanan.
|
|
207
|
+
- Menyiapkan Supabase Auth v3 dengan PKCE flow (menggantikan implicit flow).
|
|
208
|
+
- Meninjau penggunaan API key (anon key vs. service role key).
|
|
209
|
+
- Mencegah kebocoran data dari kebijakan yang salah konfigurasi.
|
|
210
|
+
- Menjalankan Supabase Linter untuk pemeriksaan keamanan otomatis.
|
|
211
|
+
|
|
212
|
+
### Supabase Auth v3 — PKCE Flow (Default 2026)
|
|
213
|
+
|
|
214
|
+
Supabase Auth v3 menggunakan **PKCE** sebagai alur default untuk semua OAuth dan magic link — menggantikan implicit flow yang lama. Konfigurasikan dengan `flowType: 'pkce'` di klien browser dan gunakan `@supabase/ssr` untuk penanganan sesi sisi server di Next.js App Router.
|
|
215
|
+
|
|
216
|
+
### Keamanan API Key
|
|
217
|
+
|
|
218
|
+
| Tipe Key | Tujuan | Tempat Penggunaan |
|
|
219
|
+
|---|---|---|
|
|
220
|
+
| `anon` key | Akses klien publik — dibatasi RLS | Frontend (browser) |
|
|
221
|
+
| `service_role` key | Melewati SEMUA RLS | Hanya backend (server) |
|
|
222
|
+
|
|
223
|
+
> ⚠️ **JANGAN PERNAH** mengekspos `service_role` key ke frontend. Jika bocor, penyerang dapat membaca/menulis semua data di database Anda, melewati RLS sepenuhnya.
|
|
224
|
+
|
|
225
|
+
### Audit Kebijakan RLS
|
|
226
|
+
|
|
227
|
+
Kesalahan RLS umum: menggunakan `USING (true)` tanpa pemeriksaan autentikasi (siapa saja, termasuk anonim, dapat membaca). Selalu verifikasi dengan `auth.role() = 'authenticated'` atau cek `auth.uid()` eksplisit.
|
|
228
|
+
|
|
229
|
+
Gunakan query audit SQL untuk menemukan tabel tanpa RLS dan tabel dengan RLS aktif tetapi tanpa kebijakan (memblokir semua akses).
|
|
230
|
+
|
|
231
|
+
### Supabase Linter
|
|
232
|
+
Jalankan `supabase db lint` untuk pemeriksaan keamanan otomatis: tabel dengan RLS dinonaktifkan, kebijakan menggunakan fungsi yang dapat dimutasi, fungsi security definer tanpa `search_path`.
|
|
233
|
+
|
|
234
|
+
### Keamanan Storage
|
|
235
|
+
Buat bucket penyimpanan privat (`public: false`) dengan batasan ukuran file dan tipe MIME yang diizinkan. Gunakan URL bertanda tangan (signed URL) untuk memberikan akses sementara ke file privat. Terapkan RLS di `storage.objects` untuk memastikan pengguna hanya dapat mengakses file mereka sendiri.
|
|
236
|
+
|
|
237
|
+
### Checklist Keamanan Produksi
|
|
238
|
+
- Semua tabel di schema `public` mengaktifkan RLS.
|
|
239
|
+
- `service_role` key hanya di variabel lingkungan sisi server.
|
|
240
|
+
- PKCE flow diaktifkan di klien Auth v3.
|
|
241
|
+
- Bucket storage privat secara default.
|
|
242
|
+
- Supabase Linter tidak ada isu kritis.
|
|
243
|
+
- MFA diaktifkan untuk pengguna admin.
|