@react-spa-scaffold/mcp 2.2.0 → 2.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.
Files changed (138) hide show
  1. package/dist/constants.d.ts +4 -0
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.js +4 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/features/definitions/api.d.ts.map +1 -1
  6. package/dist/features/definitions/api.js +2 -1
  7. package/dist/features/definitions/api.js.map +1 -1
  8. package/dist/features/definitions/database.d.ts +3 -0
  9. package/dist/features/definitions/database.d.ts.map +1 -0
  10. package/dist/features/definitions/database.js +45 -0
  11. package/dist/features/definitions/database.js.map +1 -0
  12. package/dist/features/definitions/deployment.d.ts +3 -0
  13. package/dist/features/definitions/deployment.d.ts.map +1 -0
  14. package/dist/features/definitions/deployment.js +14 -0
  15. package/dist/features/definitions/deployment.js.map +1 -0
  16. package/dist/features/definitions/electron.d.ts +3 -0
  17. package/dist/features/definitions/electron.d.ts.map +1 -0
  18. package/dist/features/definitions/electron.js +23 -0
  19. package/dist/features/definitions/electron.js.map +1 -0
  20. package/dist/features/definitions/index.d.ts +3 -0
  21. package/dist/features/definitions/index.d.ts.map +1 -1
  22. package/dist/features/definitions/index.js +3 -0
  23. package/dist/features/definitions/index.js.map +1 -1
  24. package/dist/features/registry.d.ts.map +1 -1
  25. package/dist/features/registry.js +4 -1
  26. package/dist/features/registry.js.map +1 -1
  27. package/dist/features/types.d.ts +1 -0
  28. package/dist/features/types.d.ts.map +1 -1
  29. package/dist/features/types.test.js +5 -2
  30. package/dist/features/types.test.js.map +1 -1
  31. package/dist/resources/docs.d.ts.map +1 -1
  32. package/dist/resources/docs.js +5 -0
  33. package/dist/resources/docs.js.map +1 -1
  34. package/dist/tools/add-features.js +1 -1
  35. package/dist/tools/add-features.js.map +1 -1
  36. package/dist/tools/get-features.test.js +7 -0
  37. package/dist/tools/get-features.test.js.map +1 -1
  38. package/dist/tools/get-scaffold.d.ts +1 -0
  39. package/dist/tools/get-scaffold.d.ts.map +1 -1
  40. package/dist/tools/get-scaffold.js +4 -1
  41. package/dist/tools/get-scaffold.js.map +1 -1
  42. package/dist/tools/get-scaffold.test.js +50 -0
  43. package/dist/tools/get-scaffold.test.js.map +1 -1
  44. package/dist/utils/docs.d.ts.map +1 -1
  45. package/dist/utils/docs.js +2 -0
  46. package/dist/utils/docs.js.map +1 -1
  47. package/dist/utils/scaffold/claude-md/index.d.ts.map +1 -1
  48. package/dist/utils/scaffold/claude-md/index.js +4 -1
  49. package/dist/utils/scaffold/claude-md/index.js.map +1 -1
  50. package/dist/utils/scaffold/claude-md/sections.d.ts +3 -0
  51. package/dist/utils/scaffold/claude-md/sections.d.ts.map +1 -1
  52. package/dist/utils/scaffold/claude-md/sections.js +174 -2
  53. package/dist/utils/scaffold/claude-md/sections.js.map +1 -1
  54. package/dist/utils/scaffold/compute.d.ts.map +1 -1
  55. package/dist/utils/scaffold/compute.js +4 -2
  56. package/dist/utils/scaffold/compute.js.map +1 -1
  57. package/dist/utils/scaffold/generators.d.ts +7 -2
  58. package/dist/utils/scaffold/generators.d.ts.map +1 -1
  59. package/dist/utils/scaffold/generators.js +100 -22
  60. package/dist/utils/scaffold/generators.js.map +1 -1
  61. package/package.json +1 -1
  62. package/templates/.env.example +40 -12
  63. package/templates/.github/workflows/ci.yml +49 -2
  64. package/templates/.github/workflows/deploy.yml +46 -0
  65. package/templates/CLAUDE.md +180 -1
  66. package/templates/docs/AUTHENTICATION.md +325 -0
  67. package/templates/docs/DEPLOYMENT.md +296 -0
  68. package/templates/docs/E2E_TESTING.md +81 -4
  69. package/templates/docs/SUPABASE_INTEGRATION.md +310 -0
  70. package/templates/docs/TESTING.md +195 -77
  71. package/templates/e2e/auth/auth.setup.ts +60 -0
  72. package/templates/e2e/fixtures/index.ts +11 -0
  73. package/templates/e2e/tests/profile.auth.spec.ts +103 -0
  74. package/templates/e2e/tests/profile.spec.ts +64 -0
  75. package/templates/e2e/tests/register-form.spec.ts +38 -0
  76. package/templates/forge.config.js +53 -0
  77. package/templates/gitignore +5 -0
  78. package/templates/package.json +13 -1
  79. package/templates/playwright.config.ts +33 -3
  80. package/templates/src/App.tsx +32 -19
  81. package/templates/src/components/layout/Header.test.tsx +17 -1
  82. package/templates/src/components/layout/Header.tsx +11 -0
  83. package/templates/src/components/shared/AccountButton/AccountButton.test.tsx +3 -3
  84. package/templates/src/components/shared/ProfileSync/ProfileSync.test.tsx +44 -0
  85. package/templates/src/components/shared/ProfileSync/ProfileSync.tsx +104 -0
  86. package/templates/src/components/shared/ProfileSync/index.ts +1 -0
  87. package/templates/src/components/shared/ProtectedRoute/ProtectedRoute.test.tsx +3 -3
  88. package/templates/src/components/shared/index.ts +1 -0
  89. package/templates/src/contexts/performanceContext.tsx +3 -3
  90. package/templates/src/contexts/queryContext.tsx +9 -8
  91. package/templates/src/contexts/supabaseContext.test.tsx +59 -0
  92. package/templates/src/contexts/supabaseContext.tsx +87 -0
  93. package/templates/src/hooks/index.ts +17 -0
  94. package/templates/src/hooks/supabase/index.ts +12 -0
  95. package/templates/src/hooks/supabase/useProfiles.test.tsx +207 -0
  96. package/templates/src/hooks/supabase/useProfiles.ts +213 -0
  97. package/templates/src/hooks/supabase/useSupabaseQuery.test.tsx +150 -0
  98. package/templates/src/hooks/supabase/useSupabaseQuery.ts +91 -0
  99. package/templates/src/lib/api.test.ts +30 -38
  100. package/templates/src/lib/api.ts +1 -7
  101. package/templates/src/lib/config.ts +54 -4
  102. package/templates/src/lib/env.ts +36 -14
  103. package/templates/src/lib/index.ts +4 -2
  104. package/templates/src/lib/routes.ts +1 -0
  105. package/templates/src/lib/sentry.ts +13 -10
  106. package/templates/src/lib/supabase/client.ts +58 -0
  107. package/templates/src/lib/supabase/index.ts +5 -0
  108. package/templates/src/main.ts +227 -0
  109. package/templates/src/main.tsx +32 -42
  110. package/templates/src/mocks/constants.ts +31 -0
  111. package/templates/src/mocks/fixtures/index.ts +3 -1
  112. package/templates/src/mocks/fixtures/profiles.ts +55 -0
  113. package/templates/src/mocks/fixtures/users.ts +91 -0
  114. package/templates/src/mocks/handlers/index.ts +2 -1
  115. package/templates/src/mocks/handlers/supabase.ts +64 -0
  116. package/templates/src/mocks/handlers/todos.ts +1 -1
  117. package/templates/src/mocks/index.ts +6 -0
  118. package/templates/src/pages/Profile.test.tsx +263 -0
  119. package/templates/src/pages/Profile.tsx +171 -0
  120. package/templates/src/pages/index.ts +1 -0
  121. package/templates/src/preload.ts +26 -0
  122. package/templates/src/stores/preferencesStore.ts +2 -1
  123. package/templates/src/test/clerkMock.tsx +49 -9
  124. package/templates/src/test/fetchMock.ts +58 -0
  125. package/templates/src/test/index.ts +49 -3
  126. package/templates/src/test/mocks.ts +128 -1
  127. package/templates/src/test/providers.tsx +7 -4
  128. package/templates/src/test/supabaseMock.ts +112 -0
  129. package/templates/src/test-setup.ts +26 -0
  130. package/templates/src/types/database.ts +46 -0
  131. package/templates/src/types/global.d.ts +28 -0
  132. package/templates/src/types/index.ts +1 -0
  133. package/templates/src/types/supabase.ts +167 -0
  134. package/templates/src/vite-env.d.ts +6 -0
  135. package/templates/supabase/migrations/20260104000000_create_profiles_table.sql +67 -0
  136. package/templates/vite.main.config.mjs +20 -0
  137. package/templates/vite.preload.config.mjs +17 -0
  138. package/templates/vite.renderer.config.mjs +52 -0
@@ -10,16 +10,22 @@
10
10
 
11
11
  ```
12
12
  e2e/
13
+ ├── auth/
14
+ │ └── auth.setup.ts # Clerk authentication setup
13
15
  ├── fixtures/
14
16
  │ └── index.ts # setupPage, setupCleanPage, test, expect
15
17
  ├── tests/ # Functional E2E tests
16
18
  │ ├── home.spec.ts # Page structure, accessibility
17
19
  │ ├── theme.spec.ts # Theme toggle, persistence
18
20
  │ ├── language.spec.ts # Language switcher
19
- └── navigation.spec.ts # Routing, 404
20
- └── performance/ # Performance regression tests
21
- ├── setup.ts # Performance test fixture
22
- └── home.spec.ts # Home page performance tests
21
+ ├── navigation.spec.ts # Routing, 404
22
+ │ ├── profile.spec.ts # Unauthenticated profile tests
23
+ │ └── profile.auth.spec.ts # Authenticated profile tests
24
+ ├── performance/ # Performance regression tests
25
+ │ ├── setup.ts # Performance test fixture
26
+ │ └── home.spec.ts # Home page performance tests
27
+ └── .clerk/ # Auth state storage (gitignored)
28
+ └── user.json # Saved auth state for tests
23
29
  ```
24
30
 
25
31
  ## Imports
@@ -111,8 +117,79 @@ npm run e2e:all # Run both desktop and mobile
111
117
  npm run e2e:ui # Interactive UI mode
112
118
  npm run e2e:perf # Run performance tests
113
119
  npm run e2e:perf:ui # Performance tests with interactive UI
120
+
121
+ # Authenticated tests (requires credentials)
122
+ npx playwright test --project=authenticated
123
+ ```
124
+
125
+ ## Authenticated Testing
126
+
127
+ Tests requiring authentication use `@clerk/testing` with Playwright. These tests run with a real authenticated user session.
128
+
129
+ ### Setup
130
+
131
+ 1. Install the testing package (already included):
132
+
133
+ ```bash
134
+ npm install -D @clerk/testing
135
+ ```
136
+
137
+ 2. Set environment variables in `.env`:
138
+
139
+ ```bash
140
+ CLERK_SECRET_KEY=sk_test_xxxxx
141
+ E2E_CLERK_USER_USERNAME=test@example.com
142
+ E2E_CLERK_USER_PASSWORD=your-test-password
143
+ ```
144
+
145
+ 3. Create a test user in your Clerk dashboard with the above credentials.
146
+
147
+ ### File Naming Convention
148
+
149
+ - `*.spec.ts` - Regular tests (run in `desktop`/`mobile` projects)
150
+ - `*.auth.spec.ts` - Authenticated tests (run in `authenticated` project only)
151
+
152
+ ### Writing Authenticated Tests
153
+
154
+ ```typescript
155
+ // e2e/tests/my-feature.auth.spec.ts
156
+ import { expect, test } from '@playwright/test';
157
+ import { existsSync } from 'fs';
158
+ import { dirname, join } from 'path';
159
+ import { fileURLToPath } from 'url';
160
+
161
+ const __dirname = dirname(fileURLToPath(import.meta.url));
162
+ const authFile = join(__dirname, '../.clerk/user.json');
163
+ const hasAuthState = existsSync(authFile);
164
+
165
+ test.describe('My Authenticated Feature', () => {
166
+ // Skip if auth state doesn't exist
167
+ test.skip(!hasAuthState, 'Authentication required');
168
+
169
+ test.beforeEach(async ({ page }) => {
170
+ // User is already authenticated via storageState
171
+ await page.goto('/protected-page');
172
+ });
173
+
174
+ test('can access protected content', async ({ page }) => {
175
+ await expect(page.getByText('Protected Content')).toBeVisible();
176
+ });
177
+ });
114
178
  ```
115
179
 
180
+ ### How It Works
181
+
182
+ 1. **Setup project** runs `auth.setup.ts` which:
183
+ - Calls `clerkSetup()` to get a testing token
184
+ - Signs in with test credentials
185
+ - Saves auth state to `e2e/.clerk/user.json`
186
+
187
+ 2. **Authenticated project** uses the saved state:
188
+ - Loads `storageState` from `user.json`
189
+ - Tests run with pre-authenticated session
190
+
191
+ 3. **Tests skip gracefully** when credentials aren't configured
192
+
116
193
  ## Mobile Testing
117
194
 
118
195
  Tests run on both desktop (Chrome) and mobile (Pixel 5) viewports. Use the `isMobile` fixture for device-specific behavior.
@@ -0,0 +1,310 @@
1
+ # Supabase Integration
2
+
3
+ Clerk-Supabase integration architecture, RLS patterns, and database operations.
4
+ For quick-start usage and hooks, see [CLAUDE.md](../CLAUDE.md#database-supabase).
5
+
6
+ ---
7
+
8
+ ## Architecture
9
+
10
+ ```
11
+ ClerkProvider
12
+
13
+
14
+ useSession().getToken() ──► Clerk JWT (role: authenticated)
15
+
16
+
17
+ SupabaseProvider
18
+
19
+
20
+ createClient({ accessToken: getToken }) ──► Supabase API
21
+
22
+
23
+ PostgreSQL + Row Level Security (RLS)
24
+ └── auth.uid() = Clerk user_id (JWT sub claim)
25
+ ```
26
+
27
+ **Key Design Decisions:**
28
+
29
+ - **No Supabase Auth** - Clerk handles authentication; Supabase validates JWTs via third-party provider
30
+ - **Modern `accessToken` pattern** - No JWT templates needed (post April 2025)
31
+ - **Automatic `role` claim** - Clerk integration adds `"role": "authenticated"` to JWTs, enabling RLS `TO authenticated` policies
32
+ - **Session-based client** - Client recreates only on `session?.id` change, not every render
33
+ - **TanStack Query integration** - All data fetching uses React Query for caching/invalidation
34
+ - **RLS enforcement** - Security at database level via `auth.uid()` = Clerk `user_id`
35
+
36
+ ---
37
+
38
+ ## Setup
39
+
40
+ ### 1. Enable Clerk as Third-Party Auth Provider
41
+
42
+ 1. **Clerk Dashboard** → Integrations → Supabase → Activate
43
+ 2. Copy your **Clerk domain** (e.g., `your-app.clerk.accounts.dev`)
44
+ 3. **Supabase Dashboard** → Authentication → Sign In/Up → Add provider → Clerk
45
+ 4. Paste your Clerk domain
46
+
47
+ ### 2. Environment Variables
48
+
49
+ | Variable | Description |
50
+ | ---------------------------- | ------------------------------------------- |
51
+ | `VITE_SUPABASE_DATABASE_URL` | `https://your-project.supabase.co` |
52
+ | `VITE_SUPABASE_ANON_KEY` | Client-side API key (respects RLS) |
53
+ | `SUPABASE_PROJECT_ID` | For `npm run db:types` (subdomain from URL) |
54
+
55
+ Both URL and key must be set together. Find them in Supabase Dashboard → Project Settings → Data API.
56
+
57
+ ### 3. Provider Hierarchy
58
+
59
+ ```tsx
60
+ // main.tsx - SupabaseProvider MUST be inside ClerkProvider
61
+ <ClerkProvider>
62
+ <SupabaseProvider>
63
+ <App />
64
+ </SupabaseProvider>
65
+ </ClerkProvider>
66
+ ```
67
+
68
+ ---
69
+
70
+ ## File Structure
71
+
72
+ ```
73
+ src/
74
+ ├── lib/supabase/
75
+ │ └── client.ts # createSupabaseClient(getToken)
76
+ ├── contexts/
77
+ │ └── supabaseContext.tsx # SupabaseProvider + useSupabase hook
78
+ ├── hooks/supabase/
79
+ │ ├── useSupabaseQuery.ts # Generic SELECT with TanStack Query
80
+ │ └── useProfiles.ts # Profile CRUD hooks
81
+ ├── types/
82
+ │ ├── supabase.ts # Auto-generated (npm run db:types)
83
+ │ └── database.ts # Convenience aliases (Profile, etc.)
84
+ └── components/shared/
85
+ └── ProfileSync/ # Auto-sync Clerk user to Supabase
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Database & RLS
91
+
92
+ ### Complete Table Example
93
+
94
+ ```sql
95
+ -- Create table with user_id linked to Clerk
96
+ CREATE TABLE profiles (
97
+ id TEXT PRIMARY KEY, -- Clerk user_id (auth.uid())
98
+ email TEXT NOT NULL,
99
+ full_name TEXT,
100
+ avatar_url TEXT,
101
+ created_at TIMESTAMPTZ DEFAULT NOW(),
102
+ updated_at TIMESTAMPTZ DEFAULT NOW()
103
+ );
104
+
105
+ -- Enable Row Level Security
106
+ ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
107
+
108
+ -- RLS Policies (all four CRUD operations)
109
+ CREATE POLICY "Users can view own profile"
110
+ ON profiles FOR SELECT TO authenticated
111
+ USING (id = (auth.uid())::text);
112
+
113
+ CREATE POLICY "Users can insert own profile"
114
+ ON profiles FOR INSERT TO authenticated
115
+ WITH CHECK (id = (auth.uid())::text);
116
+
117
+ CREATE POLICY "Users can update own profile"
118
+ ON profiles FOR UPDATE TO authenticated
119
+ USING (id = (auth.uid())::text)
120
+ WITH CHECK (id = (auth.uid())::text);
121
+
122
+ CREATE POLICY "Users can delete own profile"
123
+ ON profiles FOR DELETE TO authenticated
124
+ USING (id = (auth.uid())::text);
125
+
126
+ -- Auto-update timestamp trigger
127
+ CREATE OR REPLACE FUNCTION update_updated_at()
128
+ RETURNS TRIGGER AS $$
129
+ BEGIN
130
+ NEW.updated_at = NOW();
131
+ RETURN NEW;
132
+ END;
133
+ $$ LANGUAGE plpgsql;
134
+
135
+ CREATE TRIGGER profiles_updated_at
136
+ BEFORE UPDATE ON profiles
137
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at();
138
+ ```
139
+
140
+ ### RLS Policy Template
141
+
142
+ For tables where `user_id` is NOT the primary key:
143
+
144
+ ```sql
145
+ CREATE TABLE tasks (
146
+ id SERIAL PRIMARY KEY,
147
+ name TEXT NOT NULL,
148
+ user_id TEXT NOT NULL DEFAULT auth.jwt()->>'sub', -- Auto-set from Clerk
149
+ created_at TIMESTAMPTZ DEFAULT NOW()
150
+ );
151
+
152
+ ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
153
+
154
+ -- Apply this pattern for each operation (SELECT, INSERT, UPDATE, DELETE)
155
+ CREATE POLICY "Users can select own tasks"
156
+ ON tasks FOR SELECT TO authenticated
157
+ USING (user_id = (auth.uid())::text);
158
+
159
+ CREATE POLICY "Users can insert own tasks"
160
+ ON tasks FOR INSERT TO authenticated
161
+ WITH CHECK (user_id = (auth.uid())::text);
162
+
163
+ -- UPDATE needs both USING (existing rows) and WITH CHECK (new values)
164
+ -- DELETE only needs USING
165
+ ```
166
+
167
+ ### SQL Functions Reference
168
+
169
+ | Function | Returns | Use Case |
170
+ | --------------------- | ------- | ---------------------------------------------- |
171
+ | `auth.uid()` | UUID | Primary comparison (`::text` for TEXT columns) |
172
+ | `auth.jwt()` | JSON | Access full JWT claims |
173
+ | `auth.jwt()->>'sub'` | TEXT | User ID directly as text |
174
+ | `(select auth.uid())` | UUID | Performance optimization in RLS |
175
+
176
+ ### MCP Tools for Database Operations
177
+
178
+ | Tool | Purpose |
179
+ | ------------------------------------------ | ------------------------------------ |
180
+ | `mcp__supabase__apply_migration` | Apply DDL (CREATE, ALTER, DROP) |
181
+ | `mcp__supabase__execute_sql` | Run queries (SELECT, INSERT, UPDATE) |
182
+ | `mcp__supabase__list_tables` | List all tables in schemas |
183
+ | `mcp__supabase__list_migrations` | View applied migrations |
184
+ | `mcp__supabase__get_advisors` | Check security/performance issues |
185
+ | `mcp__supabase__generate_typescript_types` | Generate types from schema |
186
+
187
+ ### Type Generation
188
+
189
+ After schema changes:
190
+
191
+ ```bash
192
+ npm run db:types # Regenerates src/types/supabase.ts
193
+ ```
194
+
195
+ Add convenience aliases in `src/types/database.ts`:
196
+
197
+ ```typescript
198
+ export type Profile = Tables<'profiles'>;
199
+ export type ProfileInsert = TablesInsert<'profiles'>;
200
+ export type ProfileUpdate = TablesUpdate<'profiles'>;
201
+ ```
202
+
203
+ ---
204
+
205
+ ## React Integration
206
+
207
+ ### Hooks Reference
208
+
209
+ | Hook | Purpose | Returns |
210
+ | --------------------------- | --------------------------- | -------------------------------- |
211
+ | `useSupabase()` | Direct client access | `TypedSupabaseClient` |
212
+ | `useSupabaseQuery(options)` | Generic SELECT with caching | `UseQueryResult<T[]>` |
213
+ | `useProfile()` | Current user's profile | `{ profile, exists, isLoading }` |
214
+ | `useCurrentProfile()` | Raw profile query | `UseQueryResult<Profile[]>` |
215
+ | `useUpsertProfile()` | Create/update profile | `UseMutationResult` |
216
+ | `useUpdateProfile()` | Update current profile | `UseMutationResult` |
217
+ | `useDeleteProfile()` | Delete current profile | `UseMutationResult` |
218
+
219
+ **`useSupabaseQuery` options:** `{ table, queryKey, select?, filter?, queryOptions? }`
220
+
221
+ **Query key pattern:** All queries use `['supabase', table, ...queryKey]` for cache invalidation.
222
+
223
+ For usage examples, see [CLAUDE.md](../CLAUDE.md#database-supabase).
224
+
225
+ ### ProfileSync Component
226
+
227
+ Automatically syncs Clerk user data to Supabase on sign-in:
228
+
229
+ ```tsx
230
+ import { ProfileSync } from '@/components/shared';
231
+
232
+ function App() {
233
+ return (
234
+ <>
235
+ <ProfileSync /> {/* Optional: onSyncComplete, onSyncError callbacks */}
236
+ <Routes>...</Routes>
237
+ </>
238
+ );
239
+ }
240
+ ```
241
+
242
+ **Behavior:** Creates profile on first login, updates if email changed, runs once per mount.
243
+
244
+ ---
245
+
246
+ ## Testing
247
+
248
+ ### Mock Utilities
249
+
250
+ Import from `@/test`:
251
+
252
+ | Utility | Purpose |
253
+ | ----------------------------------------- | ---------------------------------------- |
254
+ | `setMockSupabaseData(data[])` | Set data to return |
255
+ | `setMockSupabaseError({ message, code })` | Simulate error |
256
+ | `resetSupabaseMocks()` | Reset to defaults (call in `beforeEach`) |
257
+ | `createProfile(overrides?)` | Create mock profile |
258
+ | `mockProfiles` | Pre-built profile array |
259
+
260
+ For test examples, see [CLAUDE.md](../CLAUDE.md#database-supabase).
261
+
262
+ ---
263
+
264
+ ## Security
265
+
266
+ - **Always enable RLS** on every table
267
+ - **Use `anon` key** in client code (respects RLS)
268
+ - **Never expose `service_role` key** (bypasses RLS)
269
+ - **Cast `auth.uid()` to text** when comparing with TEXT columns: `(auth.uid())::text`
270
+ - **Use `(select auth.uid())`** in policies for performance (prevents re-evaluation per row)
271
+ - **Test with multiple accounts** to verify users can't access each other's data
272
+
273
+ ---
274
+
275
+ ## Deployment
276
+
277
+ For Netlify deployment with Supabase integration, see [DEPLOYMENT.md](./DEPLOYMENT.md).
278
+
279
+ ---
280
+
281
+ ## Troubleshooting
282
+
283
+ | Issue | Cause | Fix |
284
+ | -------------------------------------------------- | --------------------------------- | ---------------------------------------------------- |
285
+ | "useSupabase must be used within SupabaseProvider" | Component outside provider | Check `main.tsx` provider order |
286
+ | 403 RLS Policy Violation | JWT missing `role: authenticated` | Verify Clerk-Supabase integration enabled |
287
+ | Empty arrays returned | RLS blocking access | Check policies use `auth.uid()` correctly |
288
+ | Stale data after mutations | Missing invalidation | Ensure `invalidateQueries(['supabase', 'profiles'])` |
289
+ | Missing env vars error | Incomplete config | Both URL and key must be set |
290
+
291
+ ### Debug Token Claims
292
+
293
+ ```typescript
294
+ const { session } = useSession();
295
+ const token = await session?.getToken();
296
+ if (token) {
297
+ const payload = JSON.parse(atob(token.split('.')[1]));
298
+ console.log(payload); // { sub: "user_xxx", role: "authenticated", ... }
299
+ }
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Resources
305
+
306
+ - [AUTHENTICATION.md](./AUTHENTICATION.md) - Clerk authentication setup and configuration
307
+ - [Supabase + Clerk Integration Guide](https://supabase.com/docs/guides/auth/third-party/clerk)
308
+ - [Row Level Security Docs](https://supabase.com/docs/guides/auth/row-level-security)
309
+ - [Clerk Supabase Docs](https://clerk.com/docs/integrations/databases/supabase)
310
+ - [Supabase MCP Server](https://supabase.com/docs/guides/getting-started/mcp)