From 276f2b0fa84042335cadbd8cb3ceb578e9a0161a Mon Sep 17 00:00:00 2001 From: Iryna Deans Date: Thu, 3 Sep 2026 13:31:55 +0200 Subject: [PATCH 1/7] Add Iryna Deans to humans.txt (#49950) ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Adds new employee as part of onboarding ## What is the current behavior? N/A ## What is the new behavior? N/A ## Additional context N/A ## Summary by CodeRabbit * **Documentation** * Added Iryna Deans to the alphabetical team member listing in the public employee directory. --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 3230223df18ac..95f1ee97d4afc 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -135,6 +135,7 @@ Hunter Luckow Ignacio Dobronich Illia Basalaiev Inian P +Iryna Deans Ivan Vasilov Jamie Boyd Jared Patterson From 479486433e28e71071b215e47f65965dac1b085a Mon Sep 17 00:00:00 2001 From: Inder Singh <85822513+singh-inder@users.noreply.github.com> Date: Thu, 3 Sep 2026 17:03:35 +0530 Subject: [PATCH 2/7] docs(self-hosted): add auth hooks guide (#43372) --- .../NavigationMenu.constants.ts | 1 + .../self-hosting/self-hosted-auth-hooks.mdx | 350 ++++++++++++++++++ .../self-hosting/self-hosted-phone-mfa.mdx | 5 +- 3 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 apps/docs/content/guides/self-hosting/self-hosted-auth-hooks.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 60f771da0bb8d..4c2982272ea91 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -3135,6 +3135,7 @@ export const self_hosting: NavMenuConstant = { { name: 'Configure Social Login (OAuth)', url: '/guides/self-hosting/self-hosted-oauth' }, { name: 'Configure Phone Login & MFA', url: '/guides/self-hosting/self-hosted-phone-mfa' }, { name: 'Add Custom Email Templates', url: '/guides/self-hosting/custom-email-templates' }, + { name: 'Configure Auth Hooks', url: '/guides/self-hosting/self-hosted-auth-hooks' }, { name: 'Configure SAML 2.0 SSO', url: '/guides/self-hosting/self-hosted-saml-sso' }, { name: 'Restore Project from Platform', diff --git a/apps/docs/content/guides/self-hosting/self-hosted-auth-hooks.mdx b/apps/docs/content/guides/self-hosting/self-hosted-auth-hooks.mdx new file mode 100644 index 0000000000000..ab5656a62cbd2 --- /dev/null +++ b/apps/docs/content/guides/self-hosting/self-hosted-auth-hooks.mdx @@ -0,0 +1,350 @@ +--- +title: 'Configure Auth Hooks' +description: 'Set up auth hooks for self-hosted Supabase with Docker.' +subtitle: 'Set up auth hooks for self-hosted Supabase with Docker.' +--- + +This guide covers the **server-side configuration** required to enable auth hooks on a self-hosted Supabase instance running with Docker Compose. Auth hooks let you run custom logic at specific points in the authentication flow - for example, adding claims to JWTs, sending SMS through a custom provider, or restricting signups. + +## Before you begin + +You need: + +- A working self-hosted Supabase installation. See [Self-Hosting with Docker](/docs/guides/self-hosting/docker). +- For Postgres function hooks: access to the database service to create functions. +- For HTTP endpoint hooks: a reachable HTTPS endpoint or a local Edge Function. + +## How hooks work + +For hook implementation details (input/output schemas, SQL and HTTP examples), see [Auth Hooks](/docs/guides/auth/auth-hooks). + +Supabase Auth can call a **hook** at specific lifecycle events during the auth flow. Each hook can be configured with the following environment variables: + +- `GOTRUE_HOOK_{HOOK_NAME}_ENABLED`: Enable the hook (`true`/`false`) +- `GOTRUE_HOOK_{HOOK_NAME}_URI`: The hook endpoint +- `GOTRUE_HOOK_{HOOK_NAME}_SECRETS`: Webhook signing secrets (for HTTP hooks) + +| Hook | Hook Name | Description | +| -------------------------------------------------------------------------------- | ------------------------------- | ---------------------------------------------------------- | +| [Custom Access Token](/docs/guides/auth/auth-hooks/custom-access-token-hook) | `CUSTOM_ACCESS_TOKEN` | Add claims to JWTs before they are issued | +| [Send SMS](/docs/guides/auth/auth-hooks/send-sms-hook) | `SEND_SMS` | Replace built-in SMS sending with a custom provider | +| [Send Email](/docs/guides/auth/auth-hooks/send-email-hook) | `SEND_EMAIL` | Replace built-in email sending with a custom provider | +| [Before User Created](/docs/guides/auth/auth-hooks/before-user-created-hook) | `BEFORE_USER_CREATED` | Run checks or block signups before creating a user | +| [MFA Verification](/docs/guides/auth/auth-hooks/mfa-verification-hook) | `MFA_VERIFICATION_ATTEMPT` | Validate MFA attempts (rate limit, brute-force protection) | +| [Password Verification](/docs/guides/auth/auth-hooks/password-verification-hook) | `PASSWORD_VERIFICATION_ATTEMPT` | Track and limit failed password attempts | + +### URI schemes + +Hooks support two URI schemes: + +| Scheme | Format | +| ----------------------- | -------------------------------------------------- | +| `pg-functions://` | `pg-functions://postgres//` | +| `http://` or `https://` | `https://example.com/hook` | + + + +Postgres function hooks run inside your database, so there is no network overhead and no need to manage secrets. + + + + + +`http://` URIs are only allowed for `localhost`, `127.0.0.1`, `::1`, and `host.docker.internal` hostnames. + + + +## Step-by-step: Postgres function hook + +This example enables the **Custom Access Token** hook using a Postgres function that adds a `user_role` claim to the JWT. + +### Step 1: Create the Postgres function + +You can execute the following SQL from the Supabase Dashboard SQL Editor, or by connecting to your database using a Postgres client such as psql. + +This example reads roles from a `user_roles` table, so create that table first. If the table is missing, the hook errors and every sign-in fails. + +```sql name=user_roles.sql +create table if not exists public.user_roles ( + user_id uuid not null references auth.users + on delete cascade, + role text not null, + primary key (user_id) +); +``` + +Then create the hook function: + +```sql name=custom_access_token_hook.sql +create or replace function public.custom_access_token_hook(event jsonb) +returns jsonb +language plpgsql +security definer +set search_path = '' +as $$ +declare + claims jsonb; + user_role text; +begin + claims := event->'claims'; + + -- Example: look up a custom role from a user_roles table + select role into user_role + from public.user_roles + where user_id = (event->>'user_id')::uuid; + + if user_role is not null then + claims := jsonb_set( + claims, '{user_role}', to_jsonb(user_role) + ); + end if; + + -- Return the modified claims + return jsonb_build_object('claims', claims); +end; +$$; + +-- Grant execute permission to supabase_auth_admin +grant execute on function public.custom_access_token_hook + to supabase_auth_admin; + +-- Grant schema access to supabase_auth_admin (usually already granted by default) +grant usage on schema public to supabase_auth_admin; + +-- Revoke from public and other roles +revoke execute on function public.custom_access_token_hook + from authenticated, anon, public; +``` + +### Step 2: Update `docker-compose.yml` + +Update the `auth` service `environment:` block: + +```yaml name=docker-compose.yml +services: + auth: + environment: + # ... existing variables ... + GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED: 'true' # πŸ‘ˆ enabling the hook is required + GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI: 'pg-functions://postgres/public/custom_access_token_hook' +``` + +### Step 3: Relaunch the auth service + +```sh +sh run.sh recreate auth +``` + +### Step 4: Verify the custom claim + +Give a user a role so the hook has something to add. Replace the UUID with a real user ID from `auth.users`: + +```sql +insert into public.user_roles (user_id, role) +values ('00000000-0000-0000-0000-000000000000', 'admin'); +``` + +Sign in as that user and decode the JWT. If the hook ran successfully, the `user_role` claim is present. A user with no matching row in `user_roles` still signs in, but without the claim. If something doesn't work, check the auth logs: + +```sh +docker compose logs auth --tail 20 +``` + +## Step-by-step: HTTP endpoint hook + +This example enables the **Send SMS** hook using an Edge Function. + + + +The Send SMS hook only fires when Auth sends an OTP. Make sure phone auth is enabled (`GOTRUE_EXTERNAL_PHONE_ENABLED=true`) and automatic phone confirmation is off (`GOTRUE_SMS_AUTOCONFIRM=false`). When `GOTRUE_SMS_AUTOCONFIRM` is on, signups are confirmed without an OTP, so the hook never runs. + + + +### Step 1: Create the Edge Function + +Create `volumes/functions/send_sms/index.ts`: + +```ts name=volumes/functions/send_sms/index.ts +import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0' + +// Note: this example assumes a single secret. If you use multiple secrets (e.g. "v1,whsec_new|v1,whsec_old"), split on '|' +// and try each secret in turn until wh.verify() succeeds. +const hookSecret = Deno.env.get('SEND_SMS_HOOK_SECRET')?.replace('v1,whsec_', '') + +Deno.serve(async (req) => { + if (req.method !== 'POST') { + return new Response('not allowed', { status: 400 }) + } + + if (!hookSecret) { + console.error('SEND_SMS_HOOK_SECRET environment variable not provided') + return new Response('{}', { status: 500 }) + } + + // Verify the webhook signature + const payload = await req.text() + const headers = Object.fromEntries(req.headers) + const wh = new Webhook(hookSecret) + const { user, sms } = wh.verify(payload, headers) + + // Send SMS using your provider + // ... your sms sending logic here ... + + return new Response(JSON.stringify({}), { + headers: { 'Content-Type': 'application/json' }, + }) +}) +``` + +### Step 2: Generate a webhook secret + +Generate a secret using the following command: + +```sh +echo "v1,whsec_$(openssl rand -base64 32)" +``` + +Copy the output of this command, for example: `v1,whsec_abc123...`. + +### Step 3: Update `.env` file + +Add the following environment variables to your `.env` file: + +```bash name=.env +SEND_SMS_HOOK_URI=http://host.docker.internal:8000/functions/v1/send_sms +SEND_SMS_HOOK_SECRET=YOUR_GENERATED_SECRET_HERE # Paste the secret generated in the last step here +``` + +### Step 4: Update `docker-compose.yml` + +Add the following environment variables to the auth and functions services. For brevity, only the updated fields are shown. + +```yaml name=docker-compose.yml +services: + auth: + environment: + # ... existing variables ... + GOTRUE_HOOK_SEND_SMS_ENABLED: 'true' + GOTRUE_HOOK_SEND_SMS_URI: ${SEND_SMS_HOOK_URI} + GOTRUE_HOOK_SEND_SMS_SECRETS: ${SEND_SMS_HOOK_SECRET} + extra_hosts: # πŸ‘ˆ required so the container can resolve host.docker.internal + - 'host.docker.internal:host-gateway' + + functions: + environment: + # ... existing variables ... + SEND_SMS_HOOK_SECRET: ${SEND_SMS_HOOK_SECRET} +``` + +### Step 5: Relaunch auth and functions services + +```sh +sh run.sh recreate auth functions +``` + +### Step 6: Verify the hook fires + +Trigger an SMS authentication event and confirm that the hook executes successfully. + +If something doesn't work, check the auth and functions logs: + +```sh +docker compose logs auth --tail 20 +docker compose logs functions --tail 20 +``` + +## Webhook secrets + +HTTP hooks use the [Standard Webhooks](https://www.standardwebhooks.com/) specification for payload signing. + +### Generating a secret + +```sh +echo "v1,whsec_$(openssl rand -base64 32)" +``` + +### Secret format + +- **Symmetric**: `v1,whsec_[base64]{32-88 characters}` + +### Key rotation + +Separate multiple secrets with `|` to rotate keys without downtime. For example: + +```bash name=.env +SEND_EMAIL_HOOK_SECRET=v1,whsec_new-secret|v1,whsec_old-secret +``` + +```yml name=docker-compose.yml +services: + auth: + environment: + # ... existing variables ... + GOTRUE_HOOK_SEND_EMAIL_ENABLED: 'true' + GOTRUE_HOOK_SEND_EMAIL_URI: 'https://example.com' + GOTRUE_HOOK_SEND_EMAIL_SECRETS: ${SEND_EMAIL_HOOK_SECRET} +``` + +The Auth service signs each request with all configured secrets, so receivers can verify against either. Once all clients accept the new secret, remove the old one. + + + +Postgres function hooks (`pg-functions://` URIs) do not require secrets - they run directly inside the database. + + + +## Troubleshooting + +### Hook not firing + +- Check that `GOTRUE_HOOK_{HOOK_NAME}_ENABLED` is set to `"true"` (as a string) in `docker-compose.yml` +- Verify the variable reaches the container: `sh run.sh printenv auth | grep GOTRUE_HOOK` +- Remember: `.env` variables do not reach the container unless passed through in `docker-compose.yml` + +### `pg-functions://` URI errors + +The URI format must be exactly `pg-functions://postgres//`: + +- Use `postgres` as the host by convention. The host segment is not validated. +- Schema and function name must be valid Postgres identifiers +- The function must exist and be granted to `supabase_auth_admin` + +### HTTP hook returns errors + +Check auth logs for details: + +```sh +docker compose logs auth --tail 20 +``` + +Common causes: + +- The endpoint is not reachable from the auth container +- `http://` is only allowed for `localhost`, `127.0.0.1`, `::1`, and `host.docker.internal` + +### Webhook secret format mismatch + +Secrets must match the Standard Webhooks format: + +- Symmetric: `v1,whsec_[base64]` (32-88 base64 characters after the prefix) +- No spaces or newlines in the secret string +- Generate with: `echo "v1,whsec_$(openssl rand -base64 32)"` + +### Permission denied on Postgres function + +The `supabase_auth_admin` role needs `execute` on the function. By default every role inherits `execute` from the `public` role, but the setup in Step 1 revokes it from `public`, so you must grant it back to `supabase_auth_admin` explicitly. Otherwise sign-in fails with `500: Error running hook URI`, and the auth logs show a permission-denied error: + +```sql +grant execute on function public.your_hook_function + to supabase_auth_admin; +``` + +The role also needs `usage` on the schema. On a default install it already has this through the built-in grant on the `public` schema, but grant it explicitly if your database has revoked usage from `public`: + +```sql +grant usage on schema public to supabase_auth_admin; +``` + +### SMS OTP expiry is too short + +Refer to [OTP Settings Docs](/docs/guides/self-hosting/self-hosted-phone-mfa#otp-settings) diff --git a/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx b/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx index 7834d4653e8a0..99eaa048419c6 100644 --- a/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx +++ b/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx @@ -76,7 +76,9 @@ Confirm your provider and credentials appear in the output. -For providers other than Twilio, add the provider-specific `GOTRUE_SMS_*` lines manually to `docker-compose.yml`. +Auth includes built-in integrations for Twilio, Twilio Verify, MessageBird, TextLocal, and Vonage. For providers other than Twilio, add the provider-specific `GOTRUE_SMS_*` lines manually to `docker-compose.yml`. + +If you want to use a regional SMS Provider, you can implement the [Send SMS Hook](/docs/guides/self-hosting/self-hosted-auth-hooks#step-by-step-http-endpoint-hook). @@ -226,4 +228,5 @@ If users see "rate limit exceeded" errors, check `SMS_MAX_FREQUENCY` (minimum in - [Multi-Factor Authentication (Phone)](/docs/guides/auth/auth-mfa/phone) - [Multi-Factor Authentication (TOTP)](/docs/guides/auth/auth-mfa/totp) +- [Self-hosted Auth hooks](/docs/guides/self-hosting/self-hosted-auth-hooks) - [Auth server on GitHub](https://github.com/supabase/auth) (check README and `example.env`) From 357d6cd3ebe2a9c340d4b86354700d1a061e3201 Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Thu, 3 Sep 2026 07:51:09 -0400 Subject: [PATCH 3/7] add github discussion link to explorer preview (#49918) Adds the GitHub discussion link for Explorer feature preview. Resolves FE-4256 ## Summary by CodeRabbit * **Bug Fixes** * Updated the Explorer & Notebooks feature preview with a working link to its GitHub discussion. --- .../interfaces/App/FeaturePreview/useFeaturePreviews.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts index fb766a922ffc7..5b3483ac31bc7 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts +++ b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts @@ -45,8 +45,7 @@ export const useFeaturePreviews = (): FeaturePreview[] => { key: LOCAL_STORAGE_KEYS.UI_PREVIEW_EXPLORER, name: 'Explorer & Notebooks', category: 'editors', - // [Joshen TODO] Update with proper URL once discussion is up - discussionsUrl: undefined, + discussionsUrl: 'https://github.com/orgs/supabase/discussions/49916', enabled: isExplorerEnabled, isNew: true, isPlatformOnly: true, From 90b7b34d7f3bc6df923f3137e0451d206968be44 Mon Sep 17 00:00:00 2001 From: Inder Singh <85822513+singh-inder@users.noreply.github.com> Date: Thu, 3 Sep 2026 17:45:08 +0530 Subject: [PATCH 4/7] docs(self-hosted): add passkeys guide (#48954) ] --- .../NavigationMenu.constants.ts | 1 + .../self-hosting/self-hosted-passkeys.mdx | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 apps/docs/content/guides/self-hosting/self-hosted-passkeys.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 4c2982272ea91..2bf96f1082661 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -3136,6 +3136,7 @@ export const self_hosting: NavMenuConstant = { { name: 'Configure Phone Login & MFA', url: '/guides/self-hosting/self-hosted-phone-mfa' }, { name: 'Add Custom Email Templates', url: '/guides/self-hosting/custom-email-templates' }, { name: 'Configure Auth Hooks', url: '/guides/self-hosting/self-hosted-auth-hooks' }, + { name: 'Configure Passkeys', url: '/guides/self-hosting/self-hosted-passkeys' }, { name: 'Configure SAML 2.0 SSO', url: '/guides/self-hosting/self-hosted-saml-sso' }, { name: 'Restore Project from Platform', diff --git a/apps/docs/content/guides/self-hosting/self-hosted-passkeys.mdx b/apps/docs/content/guides/self-hosting/self-hosted-passkeys.mdx new file mode 100644 index 0000000000000..d0757f19805e9 --- /dev/null +++ b/apps/docs/content/guides/self-hosting/self-hosted-passkeys.mdx @@ -0,0 +1,98 @@ +--- +title: 'Configure Passkey Authentication' +description: 'Set up passkey authentication for self-hosted Supabase.' +subtitle: 'Set up passkey authentication for self-hosted Supabase.' +--- + +## Overview + +[Passkeys](https://fidoalliance.org/passkeys/) are passwordless, phishing-resistant credentials built on the [WebAuthn](https://www.w3.org/TR/webauthn-3/) standard. This guide covers the server-side configuration to enable passkey authentication in a self-hosted Supabase instance. For how passkeys work and how to use them from a client, see the [Passkey authentication](/docs/guides/auth/passkeys) guide. + +## Enable passkey authentication + +For self-hosted Supabase, passkey authentication is configured through environment variables passed to the auth service in `docker-compose.yml`. The variables below control passkey authentication and WebAuthn configuration. + +| Variable | Description | Default | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | +| `GOTRUE_PASSKEY_ENABLED` | Enables passkey authentication. | `false` | +| `GOTRUE_PASSKEY_MAX_PASSKEYS_PER_USER` | Maximum number of passkeys a single user can register. | `10` | +| `GOTRUE_WEBAUTHN_RP_ID` | The bare domain name for your application (the WebAuthn [relying party](https://www.w3.org/TR/webauthn-3/#relying-party) ID). Do not include a scheme, port, or path. This determines which passkeys can be used. **Required when passkey auth is enabled.** | - | +| `GOTRUE_WEBAUTHN_RP_DISPLAY_NAME` | A human-readable name for your application, shown during the passkey prompt. **Required when passkey auth is enabled.** | - | +| `GOTRUE_WEBAUTHN_RP_ORIGINS` | Comma-separated list of allowed origins (for example, `https://example.com,https://app.example.com`). **Required when passkey auth is enabled.** | - | +| `GOTRUE_WEBAUTHN_CHALLENGE_EXPIRY_DURATION` | How long a WebAuthn challenge remains valid. If the ceremony isn't completed within this window, the client must request a new challenge. | `5m` | + +### Configure the Auth service + +Add the environment variables to the `auth` service in your `docker-compose.yml`: + +```yml name=docker-compose.yml +services: + auth: + environment: + # ... existing variables ... + GOTRUE_PASSKEY_ENABLED: true + GOTRUE_PASSKEY_MAX_PASSKEYS_PER_USER: 10 # optional - default is 10 + GOTRUE_WEBAUTHN_RP_ID: example.com + GOTRUE_WEBAUTHN_RP_DISPLAY_NAME: my-app + GOTRUE_WEBAUTHN_RP_ORIGINS: https://example.com,https://app.example.com + GOTRUE_WEBAUTHN_CHALLENGE_EXPIRY_DURATION: 5m # optional - default is 5 minutes +``` + +WebAuthn requires a secure context, so when setting `GOTRUE_WEBAUTHN_RP_ORIGINS`, keep the following requirements in mind: + +- Origins must use HTTPS, except for loopback addresses (`localhost`, `127.0.0.1`, `[::1]`). +- Each origin's hostname must match or be a subdomain of `GOTRUE_WEBAUTHN_RP_ID`. +- Android native apps can use an app origin of the form `android:apk-key-hash:`. + +For local testing, set `GOTRUE_WEBAUTHN_RP_ID` to `localhost` and use `http://localhost:3000` as the origin. + + + +Passkeys are cryptographically bound to the Relying Party (RP) ID they were registered against. Changing the RP ID makes every existing passkey unusable for sign-in, and users will need to register a new one. Pick the RP ID carefully before users start enrolling, and keep it stable once they do. + + + +### Relaunch the Auth service + +After updating the environment variables, relaunch the auth service for the changes to take effect: + +```bash +sh run.sh recreate auth +``` + +### Verify passkeys are enabled + +Request an authentication challenge to confirm the auth service picked up the configuration: + +```sh +curl -X POST 'http:///auth/v1/passkeys/authentication/options' \ + -H 'apikey: your-supabase-publishable-key' +``` + +A `200` response containing a `challenge_id` confirms that passkey authentication is enabled. A `passkey_disabled` error means the auth service did not pick up the configuration. + +## Manage a user's passkeys + +Use the Auth admin API to inspect or revoke a user's passkeys from a trusted server, for example to remove a lost device. These calls require your project's secret key, `SUPABASE_SECRET_KEY`, from your `.env` file and must never run in client code. + +The [Management API](/docs/guides/auth/passkeys#management-api) covered in the client guide targets `api.supabase.com` and isn't available for self-hosted deployments. Configure passkeys with the environment variables above, and manage individual passkeys with the admin endpoints below. + +### List a user's passkeys + +```sh +curl 'http:///auth/v1/admin/users/{user_id}/passkeys' \ + -H 'apikey: your-supabase-secret-key' +``` + +### Delete a user's passkey + +```sh +curl -X DELETE 'http:///auth/v1/admin/users/{user_id}/passkeys/{passkey_id}' \ + -H 'apikey: your-supabase-secret-key' +``` + +Deleting a user's last passkey removes their ability to sign in with a passkey until they register a new one. + +## Next steps + +After you enable passkeys in your self-hosted Auth service, configure the client exactly as you would for a hosted project: opt in when you create the Supabase client, register a passkey, and sign in. See [Enable in the client](/docs/guides/auth/passkeys#enable-in-the-client). From 6181e27b9321793ae4f4e2083afb9b5bf7dd6897 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 3 Sep 2026 14:48:05 +0200 Subject: [PATCH 5/7] Scoped PAT: ensure innaccessible resources are distinguishable (#49948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When users don't have access to some resources targeted by a token, we show those resources slugs or refs. However, they are not distinguishable enough. ## Solution - Make them distinguishable by applying the _destructive_ color - Cleaned up unused code (`isInaccessible` prop wasn't used anymore after last refactoring but we forgot to remove it) ## How to test 1. Invite another user to one of your projects 2. As this other user, create a scoped pat targeting the project 3. As the initial user, remove the invited user from the project 4. As the invited user, check the token permissions: you should see an admonition at the top and the project should be displayed in red with only its ref (not its name) ## Summary by CodeRabbit - **Bug Fixes** - Access token resource indicators now accurately show when an organization or project is inaccessible. - Inaccessible resources are clearly labeled as β€œrevoked,” reducing ambiguity about their access status. - **Style** - Organization and project access indicators now use consistent badge styling, spacing, and icon treatments. - **Accessibility** - Revoked status messages are now announced more clearly to assistive technologies. --- .../Scoped/ResourceAccessPills.tsx | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/ResourceAccessPills.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/ResourceAccessPills.tsx index 7cd5ad62a4521..9a1c69ef83d41 100644 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/ResourceAccessPills.tsx +++ b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/ResourceAccessPills.tsx @@ -1,6 +1,6 @@ import { Box, Boxes } from 'lucide-react' import { useEffect, useRef, useState } from 'react' -import { cn } from 'ui' +import { Badge, cn } from 'ui' import { OrganizationsData } from '@/data/organizations/organizations-query' import { useProjectDetailQuery } from '@/data/projects/project-detail-query' @@ -14,41 +14,53 @@ export interface ResourceAccessPillItem { export const OrganizationAccessPill = ({ slug, organization, - isInaccessible = false, }: { slug: string organization: OrganizationsData[number] | undefined - isInaccessible?: boolean -}) => ( -
- - {organization?.name ?? slug} -
-) - -export const ProjectAccessPill = ({ - projectRef, - isInaccessible = false, -}: { - projectRef: string - isInaccessible?: boolean }) => { - const { data } = useProjectDetailQuery({ ref: projectRef }) + const isInaccessible = organization == null + return ( + + + {organization?.name ?? slug} + {isInaccessible ? - revoked : null} + + ) +} + +export const ProjectAccessPill = ({ projectRef }: { projectRef: string }) => { + const { data: project, isPending } = useProjectDetailQuery({ ref: projectRef }) + const isInaccessible = project == null && !isPending return ( -
- - {data?.name ?? projectRef} -
+ + {project?.name ?? projectRef} + {isInaccessible ? - revoked : null} + ) } From f125126aec53c1ff4d4c144dccc1fa451372289d Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Thu, 3 Sep 2026 21:58:29 +0800 Subject: [PATCH 6/7] chore: make agent instructions agent-agnostic (#49941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes the repo's AI-agent setup tool-agnostic: instructions live in `AGENTS.md` files, skills live in `.agents/skills/`, and Claude Code, Codex, Cursor, and Copilot all read the same sources. Also sweeps the skills for stale and duplicated content while everything was being moved. **Changed:** - Every `CLAUDE.md` (root, `apps/studio`, `apps/docs`, `apps/kb`) is now a one-line `@AGENTS.md` import; the content moved verbatim into an `AGENTS.md` beside it. The root one moved from `.claude/CLAUDE.md` to the repo root for consistency. - All skills now live in `.agents/skills/`; `.claude/skills` is a single symlink to it (replacing the old mix of real dirs and per-skill symlinks). Path references in `.coderabbit.yaml`, code comments, and docs updated to match. - `.github/copilot-instructions.md` keeps only the review policy and points at `AGENTS.md` + `.agents/skills/`. Copilot code review reads those natively now, so the per-topic `.github/instructions/*.instructions.md` files were duplicates of the skills. - Stale skill content fixed: `studio-queries` imported a toast library Studio doesn't use, `telemetry-standards` and `studio-testing` used import paths that don't resolve, `safe-sql-execution` cited a boundary test that doesn't exist, the ask-the-docs references described an `AiPrompt` mechanism that was replaced by the ID-keyed registry, plus a handful of wrong paths, a self-contradicting `waitForTimeout` rule, an invalid Playwright signature, and a ConfigCat flag described as PostHog. - `studio-error-handling` now explains when to use `AlertError` (the default) vs `ErrorMatcher`. **Added:** - `apps/docs/AGENTS.md` (docs test requirements, from the old Cursor rule) - `studio-shortcuts` skill (from the old Copilot instruction file, verified against the current registry) - `ask-the-docs/reference/graphql-endpoint.md` and `search-embeddings.md` (from the old Cursor rules, with the missing resolver/registration/codegen steps filled in) - Feature-flag measurement section in `telemetry-standards` **Removed:** - `.cursor/` (rules folded in as above; skill symlinks no longer needed) and `.cursorignore` - `.github/instructions/` (8 files) - `vercel-composition-patterns/AGENTS.md` – a 946-line verbatim concatenation of its own `rules/` directory, and a nested `AGENTS.md` that agents could auto-load as repo instructions - `edit-the-docs/reference/structure-and-flow.md` – word-for-word copy of the skill's own Phase 2 text ## To test - `readlink .claude/skills` β†’ `../.agents/skills`, and `ls .claude/skills/copywriting/SKILL.md` resolves - Open a Claude Code session at the repo root and in `apps/studio` – the imported `AGENTS.md` content should load as before - `git diff master --stat -M` shows the skill moves as 100% renames (content unchanged except the listed fixes) - Spot-check a fixed claim, e.g. `import { toast } from 'sonner'` in `studio-queries`, or the `logs.all` ESLint rule cited in `clickhouse-logs-queries/references/codebase-integration.md` ## Summary by CodeRabbit - **Documentation** - Expanded guidance for documentation workflows, GraphQL resources, search, ClickHouse logs, React forms, Studio testing, shortcuts, telemetry, accessibility, copywriting, and composition patterns. - Clarified local testing, linting, build workflows, error handling, and AI coding agent usage. - Added contributor guidance for the knowledge base, documentation, and Studio areas. - **Chores** - Consolidated agent instructions and skill references. - Removed obsolete editor-specific guidance, duplicate links, and superseded documentation. --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com> --- .agents/skills/ask-the-docs/SKILL.md | 32 +- .../skills/ask-the-docs/reference/app-map.md | 81 +- .../ask-the-docs/reference/ci-and-lint.md | 6 +- .../skills/ask-the-docs/reference/gotchas.md | 8 +- .../reference/graphql-endpoint.md | 85 +- .../reference/llm-agent-parity.md | 39 +- .../reference/search-embeddings.md | 23 +- .../skills/clickhouse-logs-queries/SKILL.md | 0 .../references/bigquery-migration.md | 0 .../references/codebase-integration.md | 57 +- .../skills/copywriting/SKILL.md | 0 .../skills/dev-toolbar-review/SKILL.md | 8 +- .agents/skills/edit-the-docs/SKILL.md | 4 +- .../reference/structure-and-flow.md | 29 - .../reference/write-the-docs-checklist.md | 12 +- .../skills/react-hook-form/SKILL.md | 8 +- .agents/skills/review-the-docs/SKILL.md | 69 +- .../skills/safe-sql-execution/SKILL.md | 66 +- .../skills/studio-e2e-tests/SKILL.md | 10 +- .../skills/studio-error-handling/SKILL.md | 27 +- .../skills/studio-mock-api-tests/SKILL.md | 11 +- .../skills/studio-queries/SKILL.md | 2 +- .agents/skills/studio-shortcuts/SKILL.md | 63 ++ .../skills/studio-testing/SKILL.md | 27 +- .../skills/studio-ui-patterns/SKILL.md | 4 +- .../skills/telemetry-standards/SKILL.md | 44 +- .../vercel-composition-patterns/SKILL.md | 2 +- .../rules/architecture-avoid-boolean-props.md | 0 .../rules/architecture-compound-components.md | 0 .../patterns-children-over-render-props.md | 0 .../rules/patterns-explicit-variants.md | 0 .../rules/react19-no-forwardref.md | 0 .../rules/state-context-interface.md | 0 .../rules/state-decouple-implementation.md | 0 .../rules/state-lift-state.md | 0 .agents/skills/write-the-docs/SKILL.md | 2 +- .claude/skills | 1 + .claude/skills/ask-the-docs | 1 - .claude/skills/edit-the-docs | 1 - .claude/skills/pm-the-docs | 1 - .claude/skills/review-the-docs | 1 - .../vercel-composition-patterns/AGENTS.md | 946 ------------------ .claude/skills/vitest | 1 - .claude/skills/write-the-docs | 1 - .coderabbit.yaml | 10 +- .../rules/docs/docs-test-requirements/RULE.md | 25 - .cursor/skills/ask-the-docs | 1 - .cursor/skills/edit-the-docs | 1 - .cursor/skills/pm-the-docs | 1 - .cursor/skills/review-the-docs | 1 - .cursor/skills/vitest | 1 - .cursor/skills/write-the-docs | 1 - .cursorignore | 1 - .github/copilot-instructions.md | 48 +- ...tudio-composition-patterns.instructions.md | 93 -- .../instructions/studio-copy.instructions.md | 13 - .../studio-e2e-tests.instructions.md | 86 -- .../studio-error-handling.instructions.md | 43 - .../studio-shadcn-components.instructions.md | 53 - .../studio-shortcuts.instructions.md | 56 -- .../studio-telemetry.instructions.md | 60 -- .../studio-testing.instructions.md | 29 - .gitignore | 4 +- .claude/CLAUDE.md => AGENTS.md | 39 +- CLAUDE.md | 1 + apps/docs/AGENTS.md | 27 + apps/docs/CLAUDE.md | 1 + apps/docs/CONTRIBUTING.md | 6 +- apps/kb/CLAUDE.md | 2 +- apps/studio/AGENTS.md | 89 ++ apps/studio/CLAUDE.md | 89 +- apps/studio/TANSTACK_MIGRATION.md | 2 +- .../studio/data/logs/execute-analytics-sql.ts | 2 +- apps/studio/data/logs/safe-analytics-sql.ts | 2 +- apps/studio/eslint.config.cjs | 2 +- 75 files changed, 656 insertions(+), 1805 deletions(-) rename .cursor/rules/docs/docs-graphql/RULE.md => .agents/skills/ask-the-docs/reference/graphql-endpoint.md (51%) rename .cursor/rules/docs/docs-embeddings-generation/RULE.md => .agents/skills/ask-the-docs/reference/search-embeddings.md (79%) rename {.claude => .agents}/skills/clickhouse-logs-queries/SKILL.md (100%) rename {.claude => .agents}/skills/clickhouse-logs-queries/references/bigquery-migration.md (100%) rename {.claude => .agents}/skills/clickhouse-logs-queries/references/codebase-integration.md (63%) rename {.claude => .agents}/skills/copywriting/SKILL.md (100%) rename {.claude => .agents}/skills/dev-toolbar-review/SKILL.md (81%) delete mode 100644 .agents/skills/edit-the-docs/reference/structure-and-flow.md rename {.claude => .agents}/skills/react-hook-form/SKILL.md (96%) rename {.claude => .agents}/skills/safe-sql-execution/SKILL.md (87%) rename {.claude => .agents}/skills/studio-e2e-tests/SKILL.md (97%) rename {.claude => .agents}/skills/studio-error-handling/SKILL.md (52%) rename {.claude => .agents}/skills/studio-mock-api-tests/SKILL.md (96%) rename {.claude => .agents}/skills/studio-queries/SKILL.md (99%) create mode 100644 .agents/skills/studio-shortcuts/SKILL.md rename {.claude => .agents}/skills/studio-testing/SKILL.md (88%) rename {.claude => .agents}/skills/studio-ui-patterns/SKILL.md (93%) rename {.claude => .agents}/skills/telemetry-standards/SKILL.md (70%) rename {.claude => .agents}/skills/vercel-composition-patterns/SKILL.md (97%) rename {.claude => .agents}/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md (100%) rename {.claude => .agents}/skills/vercel-composition-patterns/rules/architecture-compound-components.md (100%) rename {.claude => .agents}/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md (100%) rename {.claude => .agents}/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md (100%) rename {.claude => .agents}/skills/vercel-composition-patterns/rules/react19-no-forwardref.md (100%) rename {.claude => .agents}/skills/vercel-composition-patterns/rules/state-context-interface.md (100%) rename {.claude => .agents}/skills/vercel-composition-patterns/rules/state-decouple-implementation.md (100%) rename {.claude => .agents}/skills/vercel-composition-patterns/rules/state-lift-state.md (100%) create mode 120000 .claude/skills delete mode 120000 .claude/skills/ask-the-docs delete mode 120000 .claude/skills/edit-the-docs delete mode 120000 .claude/skills/pm-the-docs delete mode 120000 .claude/skills/review-the-docs delete mode 100644 .claude/skills/vercel-composition-patterns/AGENTS.md delete mode 120000 .claude/skills/vitest delete mode 120000 .claude/skills/write-the-docs delete mode 100644 .cursor/rules/docs/docs-test-requirements/RULE.md delete mode 120000 .cursor/skills/ask-the-docs delete mode 120000 .cursor/skills/edit-the-docs delete mode 120000 .cursor/skills/pm-the-docs delete mode 120000 .cursor/skills/review-the-docs delete mode 120000 .cursor/skills/vitest delete mode 120000 .cursor/skills/write-the-docs delete mode 100644 .cursorignore delete mode 100644 .github/instructions/studio-composition-patterns.instructions.md delete mode 100644 .github/instructions/studio-copy.instructions.md delete mode 100644 .github/instructions/studio-e2e-tests.instructions.md delete mode 100644 .github/instructions/studio-error-handling.instructions.md delete mode 100644 .github/instructions/studio-shadcn-components.instructions.md delete mode 100644 .github/instructions/studio-shortcuts.instructions.md delete mode 100644 .github/instructions/studio-telemetry.instructions.md delete mode 100644 .github/instructions/studio-testing.instructions.md rename .claude/CLAUDE.md => AGENTS.md (71%) create mode 100644 CLAUDE.md create mode 100644 apps/docs/AGENTS.md create mode 100644 apps/docs/CLAUDE.md mode change 120000 => 100644 apps/kb/CLAUDE.md create mode 100644 apps/studio/AGENTS.md diff --git a/.agents/skills/ask-the-docs/SKILL.md b/.agents/skills/ask-the-docs/SKILL.md index 0de0ac2ec2a70..f7ed9c49fa4b1 100644 --- a/.agents/skills/ask-the-docs/SKILL.md +++ b/.agents/skills/ask-the-docs/SKILL.md @@ -64,8 +64,8 @@ about: - Component / data-registry relationships. - Management API OpenAPI β†’ codegen β†’ reference page flow. -Mermaid fences (`` ```mermaid `````) render natively on GitHub, Cursor, -and most Markdown previewers. Several reference files already embed +Mermaid fences (`` ```mermaid `````) render natively on GitHub and +most Markdown previewers. Several reference files already embed Mermaid; reuse or adapt them rather than re-deriving. Keep diagrams **small and one-topic**. If a diagram needs more than a @@ -76,19 +76,21 @@ dozen nodes, split it. Short, focused docs under `reference/`. Read whichever apply to the task at hand β€” they cite each other where context matters. -| File | What's inside | -| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| [`reference/adding-features.md`](./reference/adding-features.md) | Best-practices guidance for adding features to `apps/docs`. Inventory existing code first, pick the smallest viable shape, reuse pipelines. | -| [`reference/docs-app-direction.md`](./reference/docs-app-direction.md) | Refactoring vision and working norms β€” what new work should align with. | -| [`reference/known-issues.md`](./reference/known-issues.md) | Living list of broken, fragile, or in-flux systems. Check before depending on anything (federated docs, search, Sentry, reference-page architecture). | -| [`reference/app-map.md`](./reference/app-map.md) | Architecture cheat sheet β€” directories, the two-pipeline (MDX runtime + markdown export) model, heading/typography contract, telemetry, lint entries. | -| [`reference/build-pipeline.md`](./reference/build-pipeline.md) | Turborepo + pnpm lifecycle steps for building `apps/docs` β€” codegen, prebuild, postbuild, Vercel deploy. Mermaid diagram included. | -| [`reference/llm-agent-surface.md`](./reference/llm-agent-surface.md) | Audience routing, `llms.txt`, content negotiation, bulk exports. | -| [`reference/llm-agent-parity.md`](./reference/llm-agent-parity.md) | HTML↔markdown fidelity (e.g. AI prompts), search caveat, agent onboarding guides, in-flux wiring. | -| [`reference/federated-docs.md`](./reference/federated-docs.md) | How docs pulls markdown from external repos at build time. Routes, `pageMap`, remark/rehype plugins, link transforms, known failure modes. | -| [`reference/ci-and-lint.md`](./reference/ci-and-lint.md) | GitHub Actions on every PR β€” `docs_lint`, `Docs Tests`, typecheck, prettier, Vercel preview gate. Where to add a check before creating a new one. | -| [`reference/management-api-reference.md`](./reference/management-api-reference.md) | Management API OpenAPI β†’ reference generation, including scoped PAT permission tables; why not to swap in Scalar/Redoc. | -| [`reference/gotchas.md`](./reference/gotchas.md) | Specific traps to watch for. One-liner per item. | +| File | What's inside | +| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [`reference/adding-features.md`](./reference/adding-features.md) | Best-practices guidance for adding features to `apps/docs`. Inventory existing code first, pick the smallest viable shape, reuse pipelines. | +| [`reference/docs-app-direction.md`](./reference/docs-app-direction.md) | Refactoring vision and working norms β€” what new work should align with. | +| [`reference/known-issues.md`](./reference/known-issues.md) | Living list of broken, fragile, or in-flux systems. Check before depending on anything (federated docs, search, Sentry, reference-page architecture). | +| [`reference/app-map.md`](./reference/app-map.md) | Architecture cheat sheet β€” directories, the two-pipeline (MDX runtime + markdown export) model, heading/typography contract, telemetry, lint entries. | +| [`reference/build-pipeline.md`](./reference/build-pipeline.md) | Turborepo + pnpm lifecycle steps for building `apps/docs` β€” codegen, prebuild, postbuild, Vercel deploy. Mermaid diagram included. | +| [`reference/llm-agent-surface.md`](./reference/llm-agent-surface.md) | Audience routing, `llms.txt`, content negotiation, bulk exports. | +| [`reference/llm-agent-parity.md`](./reference/llm-agent-parity.md) | HTML↔markdown fidelity (e.g. AI prompts), search caveat, agent onboarding guides, in-flux wiring. | +| [`reference/federated-docs.md`](./reference/federated-docs.md) | How docs pulls markdown from external repos at build time. Routes, `pageMap`, remark/rehype plugins, link transforms, known failure modes. | +| [`reference/ci-and-lint.md`](./reference/ci-and-lint.md) | GitHub Actions on every PR β€” `docs_lint`, `Docs Tests`, typecheck, prettier, Vercel preview gate. Where to add a check before creating a new one. | +| [`reference/management-api-reference.md`](./reference/management-api-reference.md) | Management API OpenAPI β†’ reference generation, including scoped PAT permission tables; why not to swap in Scalar/Redoc. | +| [`reference/graphql-endpoint.md`](./reference/graphql-endpoint.md) | The `/api/graphql` endpoint under `apps/docs/resources/` β€” per-query folder layout, `rootSchema.ts`, connection/field utils, and the steps to add a new top-level query. | +| [`reference/search-embeddings.md`](./reference/search-embeddings.md) | The `scripts/search/` embeddings pipeline behind `searchDocs` β€” content sources, processing flow, change detection, and the `page` / `page_section` tables. | +| [`reference/gotchas.md`](./reference/gotchas.md) | Specific traps to watch for. One-liner per item. | ## How to use during a chat diff --git a/.agents/skills/ask-the-docs/reference/app-map.md b/.agents/skills/ask-the-docs/reference/app-map.md index c60e57aa420a2..25423c428ce85 100644 --- a/.agents/skills/ask-the-docs/reference/app-map.md +++ b/.agents/skills/ask-the-docs/reference/app-map.md @@ -49,28 +49,28 @@ flowchart TB ## Top-level directory layout -| Path | Purpose | Notes | -| -------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| `app/` | Next.js App Router β€” thin route files that delegate to feature modules | Slug-based catch-alls per section, e.g. `guides/auth/[[...slug]]/page.tsx` | -| `apps/docs/content/guides/` | Source MDX for `/docs/guides/...` pages | One file per page; `_partials/` for shared blocks | -| `apps/docs/content/_partials/` | Reusable MDX snippets included via `<$Partial path="..." />` | Recursion supported | -| `apps/docs/content/troubleshooting/` | Troubleshooting articles | Some synced from GitHub issues via `Troubleshooting.script.mjs` | -| `apps/docs/components/` | React components used inside MDX | One folder per component or component family | -| `apps/docs/data/` | Typed data modules consumed by components | `.data.ts` suffix; lookup helpers live in `.utils.ts`, not here | -| `apps/docs/lib/` | Pure library code shared across pipelines | Schemas (zod), helpers (`.utils.ts`), tests | -| `apps/docs/features/docs/` | Page-level skeletons and the MDX renderer (`MdxBase`) | Shared `` lives in `MdxBase.shared.tsx` | -| `apps/docs/features/` | Domain logic β€” docs rendering, auth, search/command menu, telemetry, app providers | `app.providers.tsx` wires React Query, theme, dev toolbar, command menu | -| `apps/docs/spec/` | Source-of-truth specs for reference generation | OpenAPI, SDK YAML, CLI config | -| `apps/docs/generator/` | Codegen templates for reference docs | | -| `apps/docs/resources/` | Data loaders (guide, reference, search, errors) | | -| `apps/docs/internals/` | Build-time markdown generation | **Do not import from runtime/client code** | -| `apps/docs/internals/markdown-schema/` | Per-component handlers: JSX β†’ markdown string | File name matches the component name | +| Path | Purpose | Notes | +| -------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `app/` | Next.js App Router β€” thin route files that delegate to feature modules | Slug-based catch-alls per section, e.g. `guides/auth/[[...slug]]/page.tsx` | +| `apps/docs/content/guides/` | Source MDX for `/docs/guides/...` pages | One file per page; `_partials/` for shared blocks | +| `apps/docs/content/_partials/` | Reusable MDX snippets included via `<$Partial path="..." />` | Recursion supported | +| `apps/docs/content/troubleshooting/` | Troubleshooting articles | Some synced from GitHub issues via `Troubleshooting.script.mjs` | +| `apps/docs/components/` | React components used inside MDX | One folder per component or component family | +| `apps/docs/data/` | Typed data modules consumed by components | `.data.ts` suffix; lookup helpers live in `.utils.ts`, not here | +| `apps/docs/lib/` | Pure library code shared across pipelines | Schemas (zod), helpers (`.utils.ts`), tests | +| `apps/docs/features/docs/` | Page-level skeletons and the MDX renderer (`MdxBase`) | Shared `` lives in `MdxBase.shared.tsx` | +| `apps/docs/features/` | Domain logic β€” docs rendering, auth, search/command menu, telemetry, app providers | `app.providers.tsx` wires React Query, theme, dev toolbar, command menu | +| `apps/docs/spec/` | Source-of-truth specs for reference generation | OpenAPI, SDK YAML, CLI config | +| `apps/docs/generator/` | Codegen templates for reference docs | | +| `apps/docs/resources/` | GraphQL endpoint (`/api/graphql`): per-query schema, model, resolver | See [`graphql-endpoint.md`](./graphql-endpoint.md) | +| `apps/docs/internals/` | Build-time markdown generation | **Do not import from runtime/client code** | +| `apps/docs/internals/markdown-schema/` | Per-component handlers: JSX β†’ markdown string | File name matches the component name | | `apps/docs/public/markdown/guides/` | Generated `.md` output; served via `/docs/guides/.md` or `Accept: text/markdown` | Built by `generate-guides-markdown.ts`; see [`llm-agent-surface.md`](./llm-agent-surface.md) | -| `apps/docs/public/markdown/reference/` | Generated reference `.md` files | Built by `generate-reference-markdown.ts` | -| `apps/docs/middleware.ts` | Content negotiation for guides; bot rewrite for reference deep links | Uses `packages/common/markdown-negotiation.ts` | -| `apps/docs/app/api/guides-md/` | Serves pre-generated guide markdown to agents | Rewritten from `/docs/guides/.md` | -| `apps/docs/examples/` | Copied from repo root `examples/` at build time | `codegen:examples` | -| `apps/docs/scripts/` | Build-time scripts (sitemap, markdown export, embeddings) | | +| `apps/docs/public/markdown/reference/` | Generated reference `.md` files | Built by `generate-reference-markdown.ts` | +| `apps/docs/middleware.ts` | Content negotiation for guides; bot rewrite for reference deep links | Uses `packages/common/markdown-negotiation.ts` | +| `apps/docs/app/api/guides-md/` | Serves pre-generated guide markdown to agents | Rewritten from `/docs/guides/.md` | +| `apps/docs/examples/` | Copied from repo root `examples/` at build time | `codegen:examples` | +| `apps/docs/scripts/` | Build-time scripts (sitemap, markdown export, embeddings) | | Published guide sections: `ai`, `api`, `auth`, `cron`, `database`, `deployment`, `functions`, `getting-started`, `integrations`, `local-development`, `platform`, @@ -79,12 +79,12 @@ Published guide sections: `ai`, `api`, `auth`, `cron`, `database`, `deployment`, ## Content types -| Type | Location | Notes | -| ---------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Guides / tutorials** | `content/guides/` | Hand-written MDX; goal-oriented | -| **Troubleshooting** | `content/troubleshooting/` | Partly synced from GitHub issues | +| Type | Location | Notes | +| ---------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Guides / tutorials** | `content/guides/` | Hand-written MDX; goal-oriented | +| **Troubleshooting** | `content/troubleshooting/` | Partly synced from GitHub issues | | **Reference** | Generated from `spec/` β†’ `features/docs/generated/**` | Spec-driven (OpenAPI, SDKSpec, ConfigSpec, CLISpec). Reference pages do **not** use the standard MDX path β€” see [`docs-app-direction.md`](./docs-app-direction.md) for why. Management API OpenAPI path: [`management-api-reference.md`](./management-api-reference.md). | -| **Federated** | External repos at build time | Pulled via GitHub App. See [`federated-docs.md`](./federated-docs.md) | +| **Federated** | External repos at build time | Pulled via GitHub App. See [`federated-docs.md`](./federated-docs.md) | ## Routing model @@ -128,12 +128,12 @@ outputs in sync without a parallel data shape. **Reference implementation: `ContentListings`** β€” the ID-keyed two-pipeline pattern in production: -| Layer | Path | -| ----- | ---- | -| Data registry | `apps/docs/data/content-listings/` (`.data.ts` per topic + `index.ts`) | -| Runtime component | `apps/docs/components/ContentListings/` | -| Markdown handler | `apps/docs/internals/markdown-schema/ContentListings.ts` | -| MDX registration | `apps/docs/features/docs/MdxBase.shared.tsx` | +| Layer | Path | +| ----------------- | ---------------------------------------------------------------------- | +| Data registry | `apps/docs/data/content-listings/` (`.data.ts` per topic + `index.ts`) | +| Runtime component | `apps/docs/components/ContentListings/` | +| Markdown handler | `apps/docs/internals/markdown-schema/ContentListings.ts` | +| MDX registration | `apps/docs/features/docs/MdxBase.shared.tsx` | MDX usage: ``. For batch overview-page migration, see the `audit-content-listings` skill in @@ -194,14 +194,15 @@ markdown string to substitute. See [`ci-and-lint.md`](./ci-and-lint.md) for the full CI surface. Local commands: -| Tool | Where | What it catches | -| ------------------------------------------- | ----------- | --------------------------------------------------------- | -| `pnpm test:local ` (from `apps/docs`) | per-test | Vitest suite for `lib/` and `data/` schemas | -| `pnpm format` | repo root | Prettier β€” run before opening a PR | -| `pnpm lint --filter=docs` | repo root | ESLint over `apps/docs` | -| `pnpm typecheck` | repo root | TS across packages | -| `pnpm build --filter=docs` | repo root | Includes markdown generation; failures here block release | -| `pnpm run supa-mdx-lint` | `apps/docs` | MDX content lint | +| Tool | Where | What it catches | +| --------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------- | +| `pnpm test:local:unwatch ` (from `apps/docs`) | per-test | Vitest suite for `lib/` and `data/` schemas; needs local Supabase + DB reset first β€” see `apps/docs/AGENTS.md` | +| `pnpm format` | repo root | Prettier β€” run before opening a PR | +| `pnpm lint --filter=docs` | repo root | ESLint over `apps/docs` | +| `pnpm typecheck` | repo root | TS across packages | +| `pnpm build --filter=docs` | repo root | Includes markdown generation; failures here block release | +| `pnpm lint:mdx` | `apps/docs` | MDX content lint (whole `content/` tree) | +| Typos check (`.github/workflows/avoid-typos.yml`) | CI only | `runner / misspell` job at error severity β€” no local command; fix flagged words before merge | Before adding a custom lint job, check whether the existing one can absorb the check (see [`adding-features.md`](./adding-features.md) "Reuse diff --git a/.agents/skills/ask-the-docs/reference/ci-and-lint.md b/.agents/skills/ask-the-docs/reference/ci-and-lint.md index 19e3705ea56e5..99b5f166173d2 100644 --- a/.agents/skills/ask-the-docs/reference/ci-and-lint.md +++ b/.agents/skills/ask-the-docs/reference/ci-and-lint.md @@ -81,9 +81,9 @@ Production deployment is handled by **Vercel**. Supabase employees branch the repo directly rather than fork it, so CI checks auto-run and Vercel deploys can be authorized without the external PR security gate. -Key deployment detail: the project generates markdown files for each page -under `/docs/guides/..` as a **prebuild task**. This lets Vercel bundle these -files with middleware and functions at build time. +Markdown for every guide is generated as a **prebuild task** so Vercel can +bundle it with middleware and functions β€” see +[`build-pipeline.md`](./build-pipeline.md) for the lifecycle. The **Authorize Vercel Deploys** workflow is the glue between GitHub Actions and Vercel: it runs first to approve the deploy, then Vercel picks it up and diff --git a/.agents/skills/ask-the-docs/reference/gotchas.md b/.agents/skills/ask-the-docs/reference/gotchas.md index f842cd9735cd3..54cbd296a64bc 100644 --- a/.agents/skills/ask-the-docs/reference/gotchas.md +++ b/.agents/skills/ask-the-docs/reference/gotchas.md @@ -13,10 +13,10 @@ Specific traps to watch for. One-liner per item. decisions. - **`mdxFlowExpression` / `mdxTextExpression` / `mdxjsEsm` are skipped.** Anything wrapped in `{...}` won't appear in markdown output. -- **`AiPrompt` prompts must be a `prompt={...}` prop, not children.** The - schema handler decodes the raw JS string literal from `propsFrom()` - (Prettier may emit single-quoted multiline forms). See - `internals/markdown-schema/AiPrompt.ts`. +- **`` carries only an ID.** Prompt text lives in + `data/ai-prompts.data.ts`; don't inline prompt strings in MDX. Markdown + export handles the `PromptPanel` parts via + `internals/markdown-schema/PromptPanel.ts` (drops `PromptCopy`). - **`<$Partial>` recursion is silent.** A missing or unreadable partial is dropped without error. Check `partials/` paths when content seems missing from generated `.md`. diff --git a/.cursor/rules/docs/docs-graphql/RULE.md b/.agents/skills/ask-the-docs/reference/graphql-endpoint.md similarity index 51% rename from .cursor/rules/docs/docs-graphql/RULE.md rename to .agents/skills/ask-the-docs/reference/graphql-endpoint.md index 8b16f176dc448..4dc149e86fa92 100644 --- a/.cursor/rules/docs/docs-graphql/RULE.md +++ b/.agents/skills/ask-the-docs/reference/graphql-endpoint.md @@ -1,19 +1,20 @@ ---- -description: "Docs: GraphQL architecture for apps/docs/resources" -globs: - - apps/docs/resources/**/*.ts -alwaysApply: false ---- - # Docs GraphQL Architecture +**Verify against live code** before depending on any path here β€” check +`apps/docs/resources/` and `rootSchema.ts` for the current query list. + ## Overview The `apps/docs/resources` folder contains the GraphQL endpoint architecture for the docs GraphQL endpoint at `/api/graphql`. It follows a modular pattern where each top-level query is organized into its own folder with consistent file structure. ## Architecture Pattern -Each GraphQL query follows this structure: +Each top-level query lives in its own folder. `error/` is the fullest +example; `*Types.ts` and `*Sync.ts` are optional, and `globalSearch/` +uses a `*Interface.ts` for its polymorphic result type instead. `guide/`, +`reference/`, and `troubleshooting/` hold only model + schema files: they +are result types surfaced through `searchDocs`, registered under `types` +in `rootSchema.ts`, not top-level queries. ``` resources/ @@ -30,11 +31,16 @@ resources/ └── rootSync.ts # Root sync script for syncing to database ``` -## Example queries +## Folders and top-level queries -1. **searchDocs** (`globalSearch/`) - Vector-based search across all docs content -2. **error** (`error/`) - Error code lookup for Supabase services -3. **schema** - GraphQL schema introspection +| Folder | Exposes | +| ------------------ | ------------------------------------------------------------------------------------ | +| `globalSearch/` | **searchDocs** β€” vector search across all docs content | +| `error/` | **error** (single code lookup) and **errors** (paginated collection) | +| `guide/` | `Guide` result type (search result), no top-level query | +| `reference/` | `ReferenceCLICommand`, `ReferenceManagementApi`, `ReferenceSDKFunction` result types | +| `troubleshooting/` | `Troubleshooting` result type | +| `rootSchema.ts` | **schema** β€” introspection, plus the root that spreads the query objects above | ## Key Files @@ -97,7 +103,7 @@ export const GraphQLObjectTypeNewQuery = new GraphQLObjectType({ > [!TIP] > The types in `~/__generated__/graphql` for a new endpoint will not exist -> until the code generation is run in the next step. +> until the code generation in step 6 has run. ```typescript import { type RootQueryTypeNewQueryArgs } from '~/__generated__/graphql' @@ -119,9 +125,7 @@ export class NewQueryModel { ): Promise> { // Implement data fetching logic const result = new Result( - await supabase() - .from('your_table') - .select('*') + await supabase().from('your_table').select('*') // Add filters based on args ) .map((data) => data.map((item) => new NewQueryModel(item))) @@ -130,3 +134,52 @@ export class NewQueryModel { } } ``` + +### 4. Write the resolver (`newQueryResolver.ts`) + +Mirror `error/errorResolver.ts`: wrap the model call in +`Result.tryCatchFlat(..., convertUnknownToApiError, args)`, log and +`Sentry.captureException` non-user errors, and return a `GraphQLError` +whose message is `'Internal Server Error'` when `error.isPrivate()`. For +paginated results use `paginationArgs`, `createCollectionType()`, and +`GraphQLCollectionBuilder.create()` from `utils/connections.ts`. Export a +root-field object keyed by the field constant: + +```typescript +export const newQueryRoot = { + [GRAPHQL_FIELD_NEW_QUERY]: { + description: 'What this query returns', + args: { id: { type: new GraphQLNonNull(GraphQLString) } }, + type: GraphQLObjectTypeNewQuery, + resolve: resolveNewQuery, + }, +} +``` + +### 5. Register it in `rootSchema.ts` + +Spread the root-field object into `RootQueryType.fields` next to +`...errorRoot`. Object types that are only reachable through an +interface (search results) go in the `types` array instead. + +### 6. Run codegen + +```bash +cd apps/docs && pnpm run codegen:graphql +``` + +This prints the schema to `__generated__/schema.graphql` +(`scripts/graphqlSchema.ts`) and runs `graphql-codegen` (`codegen.ts`) to +produce `~/__generated__/graphql` β€” the `RootQueryType*Args` and resolver +types the model and resolver import. `predev` and `prebuild` run this +automatically. + +## Related + +- [`app-map.md`](./app-map.md) β€” where `resources/` sits in the app layout. +- [`llm-agent-surface.md`](./llm-agent-surface.md) β€” `searchDocs` as an + agent entry point. +- [`search-embeddings.md`](./search-embeddings.md) β€” the offline pipeline + that populates what `searchDocs` queries. +- [`build-pipeline.md`](./build-pipeline.md) β€” where `codegen:graphql` runs + in `predev` / `prebuild`. diff --git a/.agents/skills/ask-the-docs/reference/llm-agent-parity.md b/.agents/skills/ask-the-docs/reference/llm-agent-parity.md index 6c66a4caf3d4e..49f5008c8fb8d 100644 --- a/.agents/skills/ask-the-docs/reference/llm-agent-parity.md +++ b/.agents/skills/ask-the-docs/reference/llm-agent-parity.md @@ -12,38 +12,29 @@ Content that renders on the HTML page is **not** automatically present in the `.md` export. Both pipelines must implement the same semantics. Example: framework quickstart **AI prompt blocks** (see -supabase/supabase#47543). Each quickstart includes a bare partial: +supabase/supabase#47543). Each quickstart embeds one by ID: ```mdx -<$Partial path="ai/quickstart_prompt_nextjs.mdx" /> + ``` -The partial is a self-closing `` with the prompt as a **prop** -(not children β€” expression children are skipped by the guides markdown -pipeline): +The prompt text lives in `data/ai-prompts.data.ts` (`aiPrompts`, keyed by +`id`); the MDX carries only the ID, following the registry pattern in +[`app-map.md`](./app-map.md). An unknown ID throws at render time. -```mdx - -``` - -| Pipeline | Path | -| ------------ | ------------------------------------------------------------ | -| **HTML** | `features/ui/AiPrompt.tsx` β†’ `PromptPanel` (copy + expand) | -| **Markdown** | `internals/markdown-schema/AiPrompt.ts` reads `props.prompt` | +| Pipeline | Path | +| ------------ | --------------------------------------------------------------------------------------------------------------------------------------- | +| **HTML** | `features/ui/AiPrompt.tsx` looks up the prompt and renders `PromptPanel` (copy + expand) | +| **Markdown** | `internals/markdown-schema/PromptPanel.ts` β€” handlers for the `PromptPanel` compound parts, registered in `generate-guides-markdown.ts` | -`propsFrom()` stores the raw JS expression source. Prettier formats the -prop as a multiline **single-quoted** string; the schema handler must -decode that literal (trim + escapes), not only `JSON.parse` double-quoted -JSON. Symptom if decoding is wrong: generated `.md` keeps surrounding -quotes and literal `\n`. +The markdown handlers keep `PromptTitle` (bold) and `PromptContent`, and +drop `PromptCopy` (clipboard-only duplicate). `AiPrompt` itself has no +markdown handler: the HTML wrapper composes the compound children on the +client, so the `.md` export intentionally omits the copy panel. `$Partial` variable substitution (`lib/partials.utils.ts`, wired in both -`partialsRemark` and `inlinePartials`) remains required for other nested -partials β€” it is no longer the AI-prompt path. +`partialsRemark` and `inlinePartials`) is still required for nested +partials; it is not involved in the AI-prompt path. When adding content aimed at both humans and agents, always verify: diff --git a/.cursor/rules/docs/docs-embeddings-generation/RULE.md b/.agents/skills/ask-the-docs/reference/search-embeddings.md similarity index 79% rename from .cursor/rules/docs/docs-embeddings-generation/RULE.md rename to .agents/skills/ask-the-docs/reference/search-embeddings.md index 6eab6f71510d0..37983f45f024e 100644 --- a/.cursor/rules/docs/docs-embeddings-generation/RULE.md +++ b/.agents/skills/ask-the-docs/reference/search-embeddings.md @@ -1,12 +1,10 @@ ---- -description: "Docs: embeddings generation pipeline (apps/docs/scripts/search)" -globs: - - apps/docs/scripts/search/**/*.ts -alwaysApply: false ---- - # Documentation Embeddings Generation System +**Verify against live code** before depending on any path here. Search +infrastructure is on the [`known-issues.md`](./known-issues.md) list +(decoupled from the markdown export pipeline and considered fragile) β€” +check there before building on it. + ## Overview The documentation embeddings generation system processes various documentation sources and uploads their metadata to a database for semantic search functionality. The system is located in `apps/docs/scripts/search/` and works by: @@ -66,3 +64,14 @@ The documentation embeddings generation system processes various documentation s - **`page`** table: Stores page metadata, content, checksum, version - **`page_section`** table: Stores individual sections with embeddings, token counts + +## Related + +- [`known-issues.md`](./known-issues.md) β€” search infrastructure fragility. +- [`llm-agent-parity.md`](./llm-agent-parity.md) β€” `searchDocs` caveats for + agents. +- [`graphql-endpoint.md`](./graphql-endpoint.md) β€” the `searchDocs` query + that reads these embeddings. +- [`build-pipeline.md`](./build-pipeline.md) β€” embeddings are generated + offline (`pnpm run embeddings`, run by `.github/workflows/search.yml`), + not as part of the site build. diff --git a/.claude/skills/clickhouse-logs-queries/SKILL.md b/.agents/skills/clickhouse-logs-queries/SKILL.md similarity index 100% rename from .claude/skills/clickhouse-logs-queries/SKILL.md rename to .agents/skills/clickhouse-logs-queries/SKILL.md diff --git a/.claude/skills/clickhouse-logs-queries/references/bigquery-migration.md b/.agents/skills/clickhouse-logs-queries/references/bigquery-migration.md similarity index 100% rename from .claude/skills/clickhouse-logs-queries/references/bigquery-migration.md rename to .agents/skills/clickhouse-logs-queries/references/bigquery-migration.md diff --git a/.claude/skills/clickhouse-logs-queries/references/codebase-integration.md b/.agents/skills/clickhouse-logs-queries/references/codebase-integration.md similarity index 63% rename from .claude/skills/clickhouse-logs-queries/references/codebase-integration.md rename to .agents/skills/clickhouse-logs-queries/references/codebase-integration.md index 05c55fa976740..d49866ffe560b 100644 --- a/.claude/skills/clickhouse-logs-queries/references/codebase-integration.md +++ b/.agents/skills/clickhouse-logs-queries/references/codebase-integration.md @@ -8,8 +8,13 @@ just writing a query in the Logs Explorer UI. ## Branded SQL: never concatenate user input All analytics log SQL must be a `SafeLogSqlFragment`, built with the helpers in -`apps/studio/data/logs/safe-analytics-sql.ts`. This is enforced by eslint, and the -branding is what keeps interpolated values from becoming injection. The key +`apps/studio/data/logs/safe-analytics-sql.ts`. Two things enforce this: the +`sql` parameter of `executeAnalyticsSql` (`apps/studio/data/logs/execute-analytics-sql.ts`) +is typed `SafeLogSqlFragment`, and an eslint `no-restricted-syntax` rule in +`apps/studio/eslint.config.cjs` blocks direct `post()`/`get()` calls to the +`logs.all` / `logs.all.otel` endpoints from any file other than +`execute-analytics-sql.ts`. The branding is what keeps interpolated values from +becoming injection. The key exports: - `safeSql\`...\``β€” a tagged template that only accepts`SafeLogSqlFragment`interpolations. Plain strings (and Postgres-branded`SafeSqlFragment`) are @@ -25,11 +30,19 @@ exports: - `quotedIdent(value)` β€” backtick-quotes a dotted identifier path after validating each segment. -There is intentionally no exported "raw" escape hatch. Compose with `safeSql` plus -these helpers. +Compose with `safeSql` plus these helpers. The only way to run SQL that was not +built from them is the user-authored path: `untrustedLogSql(text)` marks editor +text as `UntrustedLogSqlFragment` (displayable, storable, never executable), and +`acceptUntrustedLogsSql(fragment)` promotes it to `SafeLogSqlFragment`. That +promotion is a security boundary β€” call it only from a run gesture (Run +button click, Cmd+Enter) or an approval-gated tool call (the AI notebook +tools). Never from render, `useEffect`, or any automatic path. The notebook +persist path also promotes cells because the writable notebook type requires +`SafeLogSqlFragment`; that is storage typing, not execution approval, and is +not precedent for promoting anywhere else. ```ts -import { analyticsLiteral, safeSql } from 'data/logs/safe-analytics-sql' +import { analyticsLiteral, safeSql } from '@/data/logs/safe-analytics-sql' const source = 'edge_logs' const sql = safeSql` @@ -43,8 +56,8 @@ const sql = safeSql` ## Pick the endpoint and builder by flag -The ClickHouse path is gated by the `otelLegacyLogs` PostHog flag -(`useFlag('otelLegacyLogs')` from `common`). Keep the BigQuery path working when +The ClickHouse path is gated by the `otelLegacyLogs` ConfigCat flag +(`useFlag('otelLegacyLogs')` from `common` β€” ConfigCat, not PostHog). Keep the BigQuery path working when the flag is off. Two helpers in `apps/studio/data/logs/logs-endpoint.ts` express the split: @@ -61,7 +74,35 @@ const endpoint = logsAllEndpointUrl(useOtel) ``` Run the fragment through `executeAnalyticsSql` (`apps/studio/data/logs/execute-analytics-sql.ts`) -against that endpoint. +against that endpoint: + +```ts +import { executeAnalyticsSql } from '@/data/logs/execute-analytics-sql' +import { analyticsLiteral, quotedIdent, safeSql } from '@/data/logs/safe-analytics-sql' + +// βœ… GOOD: every interpolation is sanitized. +const sql = safeSql` + SELECT timestamp, event_message + FROM ${quotedIdent(table)} + WHERE id = ${analyticsLiteral(id)} +` + +await executeAnalyticsSql({ + projectRef, + endpoint, + sql, + iso_timestamp_start, + iso_timestamp_end, +}) +``` + +```ts +// πŸ›‘ BAD: raw string interpolation. This fails to type-check at the +// executeAnalyticsSql boundary because the result is `string`, not +// `SafeLogSqlFragment`. +const sql = `SELECT * FROM ${table} WHERE id = '${id}'` +await executeAnalyticsSql({ projectRef, endpoint, sql, iso_timestamp_start, iso_timestamp_end }) +``` ## Follow the existing OTEL builders diff --git a/.claude/skills/copywriting/SKILL.md b/.agents/skills/copywriting/SKILL.md similarity index 100% rename from .claude/skills/copywriting/SKILL.md rename to .agents/skills/copywriting/SKILL.md diff --git a/.claude/skills/dev-toolbar-review/SKILL.md b/.agents/skills/dev-toolbar-review/SKILL.md similarity index 81% rename from .claude/skills/dev-toolbar-review/SKILL.md rename to .agents/skills/dev-toolbar-review/SKILL.md index ae6b81d10ab1d..fae8499208840 100644 --- a/.claude/skills/dev-toolbar-review/SKILL.md +++ b/.agents/skills/dev-toolbar-review/SKILL.md @@ -10,7 +10,7 @@ description: Safety rules for the dev toolbar, PostHog client, and feature flags Review checklist for PRs touching the dev toolbar (`packages/dev-tools/`) and its integration points in `packages/common/`. The toolbar surfaces telemetry events and -allows feature flag overrides during local development (expanding to staging/preview). +allows feature flag overrides in local and staging environments only. ## When This Applies @@ -32,12 +32,12 @@ so PRs touching only those files won't auto-request review. Watch for these in t The toolbar uses two layers of protection: -- **Build-time tree-shaking** in `index.ts`: `process.env.NODE_ENV !== 'development'` ternaries that replace components with noops/stubs so the implementation is eliminated from production bundles. -- **Runtime guards** in components: `IS_LOCAL_DEV` checks β€” `DevToolbar` and `DevToolbarTrigger` return `null` to hide themselves, while `DevToolbarProvider` passes children through (`<>{children}`) to preserve the component tree. +- **Build-time tree-shaking** in `index.ts`: the bundler inlines `process.env.NEXT_PUBLIC_ENVIRONMENT`, so `isToolbarEnabled = env === 'local' || env === 'staging'` makes the export ternaries static. Outside those two environments every export is a stub (`DevToolbar` and `DevToolbarTrigger` render `null`, `DevToolbarProvider` passes children through, `useDevToolbar` returns a no-op context) and the implementation modules are eliminated from the bundle. The same literal `process.env` check is duplicated in `DevToolbarContext.tsx`, `DevToolbar.tsx`, `DevToolbarTrigger.tsx`, and `feature-flags.tsx` because the bundler must see it directly β€” keep them in sync. +- **Runtime guards** inside the implementation: `IS_LOCAL_DEV = env === 'local'` gates the local-only pieces β€” the SSE event stream in `DevToolbarContext.tsx` and local-only UI in `DevToolbar.tsx` β€” so staging gets the toolbar without them. **Check for:** -- Guards being removed or broadened. The toolbar is expanding to staging and preview deploys but must remain invisible in production. +- Guards being removed or broadened. The toolbar is enabled only for `local` and `staging`; preview and production builds must keep getting the stubs. - Tree-shaking ternaries in `index.ts` staying intact β€” these are the primary production safety mechanism. - New components or exports that bypass the existing guard pattern. diff --git a/.agents/skills/edit-the-docs/SKILL.md b/.agents/skills/edit-the-docs/SKILL.md index 6c8df9905779a..24f33ca07c422 100644 --- a/.agents/skills/edit-the-docs/SKILL.md +++ b/.agents/skills/edit-the-docs/SKILL.md @@ -33,7 +33,7 @@ and clarity. Distinct from [`write-the-docs`](../write-the-docs/SKILL.md) ## Phase 2 β€” Restructure -Apply [reference/structure-and-flow.md](reference/structure-and-flow.md): +Apply the **Mixed information types**, **Navigation**, and **Cross-references and glue** guidance in [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md) (Guides section), summarized here: 1. Classify substantial sections as contextual, procedural, or reference content. In a mixed page, group sections by information type so that context doesn't interrupt the procedural path. 2. For a long or mixed page, add a short introduction that links to its major section groups and tells readers when to use each one. Skip this navigation when a short page is already easy to scan. @@ -66,7 +66,7 @@ Then run [`review-the-docs`](../review-the-docs/SKILL.md) local self-review (`pn ## Additional resources - Structure SoT: [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md) (mixed types, navigation, glue) -- Structure ops: [reference/structure-and-flow.md](reference/structure-and-flow.md) +- Structure ops: [`apps/docs/CONTRIBUTING.md`](../../../apps/docs/CONTRIBUTING.md) β€” Guides: Mixed information types, Navigation, Cross-references and glue - Pitfalls: [`write-the-docs/reference/common-pitfalls.md`](../write-the-docs/reference/common-pitfalls.md) - Mechanics: [`write-the-docs/reference/drafting-mechanics.md`](../write-the-docs/reference/drafting-mechanics.md) - Architecture/IA: [`ask-the-docs`](../ask-the-docs/SKILL.md) diff --git a/.agents/skills/edit-the-docs/reference/structure-and-flow.md b/.agents/skills/edit-the-docs/reference/structure-and-flow.md deleted file mode 100644 index 57cad4b37a869..0000000000000 --- a/.agents/skills/edit-the-docs/reference/structure-and-flow.md +++ /dev/null @@ -1,29 +0,0 @@ -# Structure and flow - -Operational guidance for restructuring existing docs pages. The human-facing -source of truth is [`apps/docs/CONTRIBUTING.md`](../../../../apps/docs/CONTRIBUTING.md) -under Guides: **Mixed information types**, **Navigation**, and -**Cross-references and glue**. Keep this file aligned with that section. - -## Classify and group - -Classify substantial sections as contextual, procedural, or reference content. -In a mixed page, group sections by information type so that context doesn't -interrupt the procedural path. - -## Introduction navigation - -For a long or mixed page, add a short introduction that links to its major -section groups and tells readers when to use each one. Skip this navigation -when a short page is already easy to scan. - -## Connective text - -Connect contextual sections to their corresponding procedures when useful. -Add introductions to section groups, transitions between information types, -and outcomes after procedures. Don't link every adjacent section. - -## Voice and procedure shape - -Use second person, present tense, short paragraphs, and ordered steps for -sequential actions. diff --git a/.agents/skills/pm-the-docs/reference/write-the-docs-checklist.md b/.agents/skills/pm-the-docs/reference/write-the-docs-checklist.md index caefd757d4adc..5bffc867c62be 100644 --- a/.agents/skills/pm-the-docs/reference/write-the-docs-checklist.md +++ b/.agents/skills/pm-the-docs/reference/write-the-docs-checklist.md @@ -16,7 +16,7 @@ A practical six-stage checklist and quality standard for planning, drafting, and ## 1. Frame -_Skills:_ `/ask-the-docs` to see how the surface works today; `/pm-the-docs` for audience, stage, and cross-cutting scope calls. +_Skills:_ `ask-the-docs` to see how the surface works today; `pm-the-docs` for audience, stage, and cross-cutting scope calls. - [ ] P: State the product stage (private/public alpha, beta, GA) - [ ] P: Name the audience and the job they are trying to do @@ -24,7 +24,7 @@ _Skills:_ `/ask-the-docs` to see how the surface works today; `/pm-the-docs` for ## 2. Shape -_Skill:_ `/ask-the-docs` for IA placement, architecture, and where content lives. +_Skill:_ `ask-the-docs` for IA placement, architecture, and where content lives. - [ ] P: Pick the content type(s): tutorial (learning), how-to (a task), reference (lookup), explanation (the why). Do not mix types on one page (refer to [DiΓ‘taxis](https://diataxis.fr/)) - [ ] P: Decide where the page lives in the existing IA and what links in and out (avoid orphan pages) @@ -32,25 +32,25 @@ _Skill:_ `/ask-the-docs` for IA placement, architecture, and where content lives ## 3. Draft -_Skill:_ `/write-the-docs` to draft net-new content grounded in Linear and the code. +_Skill:_ `write-the-docs` to draft net-new content grounded in Linear and the code. - [ ] P: Lead with the why and the outcome, then the how/what (product story first) - [ ] P: Include at least one runnable, copy-pasteable example that you have actually run - [ ] E: Contribute technical depth and verify accuracy (APIs, limits, edge cases) - [ ] P: Call out the current stage inline and any known limitations -When the work is improving an existing page (restructure, reorder, connective text, brevity) rather than authoring net-new content, use `/edit-the-docs` instead of `/write-the-docs`. +When the work is improving an existing page (restructure, reorder, connective text, brevity) rather than authoring net-new content, use `edit-the-docs` instead of `write-the-docs`. ## 4. Self-review against the bar -_Skill:_ `/review-the-docs` β€” [Local self-review](../review-the-docs/SKILL.md#local-self-review-no-open-pr) on your own branch before opening the PR. +_Skill:_ `review-the-docs` β€” [Local self-review](../../review-the-docs/SKILL.md#local-self-review-no-open-pr) on your own branch before opening the PR. - [ ] P/E: Check the draft against "What good looks like" above before opening the PR - [ ] P/E: Follow authoring-experience standards and tooling when available ## 5. PR review -_Skill:_ `/review-the-docs` to triage, classify, verify the build, and report. +_Skill:_ `review-the-docs` to triage, classify, verify the build, and report. - [ ] P/E: Open the PR and request review per the rules of engagement - [ ] Docs: Review against the published bar diff --git a/.claude/skills/react-hook-form/SKILL.md b/.agents/skills/react-hook-form/SKILL.md similarity index 96% rename from .claude/skills/react-hook-form/SKILL.md rename to .agents/skills/react-hook-form/SKILL.md index c08c5037a2b4b..064fd1fd45439 100644 --- a/.claude/skills/react-hook-form/SKILL.md +++ b/.agents/skills/react-hook-form/SKILL.md @@ -162,7 +162,13 @@ buttons. computing `defaultValues` from a query that may not have loaded freezes whatever happened to be in cache at mount. Add `resetOptions: { keepDirtyValues: true }` when a background refetch must not - clobber the user's in-progress edits. (Good examples: + clobber the user's in-progress edits. `keepDirtyValues` preserves whatever is + in `formState.dirtyFields`; typed edits and `setValue(…, { shouldDirty: true })` + populate it regardless of subscriptions, but `useFieldArray` operations + (append/remove/move) only mark fields dirty while `dirtyFields` or `isDirty` + is subscribed β€” so a form that combines `keepDirtyValues` with a field array + must read one of them in the owner, or the next refetch will discard array + edits. (Good examples: `components/interfaces/Settings/Database/ConnectionLogging.tsx`, `components/interfaces/Storage/EditBucketModal.tsx`.) - **After a successful mutation, re-baseline the form** in `onSuccess` so the diff --git a/.agents/skills/review-the-docs/SKILL.md b/.agents/skills/review-the-docs/SKILL.md index 1b6bdcba37c78..2b42ac33c50ce 100644 --- a/.agents/skills/review-the-docs/SKILL.md +++ b/.agents/skills/review-the-docs/SKILL.md @@ -1,7 +1,7 @@ --- name: review-the-docs description: >- - Review Supabase docs changes locally in ~/GitHub/supabase/supabase β€” + Review Supabase docs changes locally in your `supabase/supabase` checkout β€” either an open PR (triage, classify, verify) or your own branch before opening a PR (local self-review). Covers markdown pipeline, MDX content, tutorials, examples, Studio links, and docs tooling. Use when asked to @@ -31,24 +31,24 @@ For **implementing** docs fixes (Linear tickets, worktrees, platform E2E), use [ ## Repository layout -| Path | Purpose | -| -------------------------------------- | --------------------------------------------------------- | -| `~/GitHub/supabase/supabase` | Main clone for review checkouts | -| `apps/docs/content/guides/` | Source MDX | -| `apps/docs/internals/` | Markdown pipeline (`generate-guides-markdown.ts`, etc.) | -| `apps/docs/internals/markdown-schema/` | Component handlers β†’ plain markdown strings | -| `apps/docs/public/markdown/guides/` | Generated output (produced by build) | -| `apps/docs/components/` | React MDX components | -| `examples/` | Tutorial/quickstart apps referenced via `$CodeSample` | -| `apps/studio/` | Dashboard UI; may link to hosted docs | -| `.agents/skills/` | In-repo agent skills (symlinked from `.claude`/`.cursor`) | +| Path | Purpose | +| --------------------------------------- | ---------------------------------------------------------------- | +| your local `supabase/supabase` checkout | Main clone for review checkouts | +| `apps/docs/content/guides/` | Source MDX | +| `apps/docs/internals/` | Markdown pipeline (`generate-guides-markdown.ts`, etc.) | +| `apps/docs/internals/markdown-schema/` | Component handlers β†’ plain markdown strings | +| `apps/docs/public/markdown/guides/` | Generated output (produced by build) | +| `apps/docs/components/` | React MDX components | +| `examples/` | Tutorial/quickstart apps referenced via `$CodeSample` | +| `apps/studio/` | Dashboard UI; may link to hosted docs | +| `.agents/skills/` | In-repo agent skills (canonical; `.claude/skills` symlinks here) | ## Local self-review (no open PR) Use this on your own branch **before** opening a PR (checklist Stage 4). No `gh pr` required. ```bash -cd ~/GitHub/supabase/supabase +cd # Ensure you're on the feature branch, not master git branch --show-current git diff --name-only master...HEAD @@ -59,8 +59,8 @@ git diff --name-only master...HEAD 3. **Run type-specific checks** from the matching sections below on the current branch (no checkout step). Typical commands: ```bash -# Content / tutorial MDX -cd apps/docs && pnpm lint:mdx -- +# Content / tutorial MDX (lints the whole content/ tree; no per-file scoping) +cd apps/docs && pnpm lint:mdx # Pipeline / schema handler cd apps/docs && pnpm build:guides-markdown @@ -108,17 +108,17 @@ Inspect changed files from `gh pr view` or: gh pr diff --repo supabase/supabase --name-only ``` -| PR type | Path signals | Primary skill section | -| --------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | -| **Markdown-schema handler** | `apps/docs/internals/markdown-schema/`, `generate-guides-markdown.ts` | [Schema handler review](#schema-handler-review) | -| **Pipeline / internals** | `apps/docs/internals/` (not just one new handler) | [Pipeline review](#pipeline-review) | -| **Content-only MDX** | `apps/docs/content/**` only | [Content review](#content-review) | -| **Tutorial / quickstart** | `apps/docs/content/guides/**/tutorials/`, `quickstarts/`, plus `examples/` | [Tutorial review](#tutorial-review) β†’ also `work-linear-issue` | -| **Example app only** | `examples/**` without matching MDX | [Example review](#example-review) | -| **Studio ↔ docs links** | `apps/studio/**` | [Studio review](#studio-review) | -| **Docs UI / components** | `apps/docs/components/`, `apps/docs/features/` (no pipeline) | [Component review](#component-review) | -| **Docs tooling** | `.agents/skills/`, `.claude/skills/`, `.cursor/skills/`, `apps/docs/CONTRIBUTING.md`, `apps/docs/DEVELOPERS.md` | [Docs tooling review](#docs-tooling-review) | -| **Mixed** | Multiple path groups above | Run each applicable section; note overlap | +| PR type | Path signals | Primary skill section | +| --------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | +| **Markdown-schema handler** | `apps/docs/internals/markdown-schema/`, `generate-guides-markdown.ts` | [Schema handler review](#schema-handler-review) | +| **Pipeline / internals** | `apps/docs/internals/` (not just one new handler) | [Pipeline review](#pipeline-review) | +| **Content-only MDX** | `apps/docs/content/**` only | [Content review](#content-review) | +| **Tutorial / quickstart** | `apps/docs/content/guides/**/tutorials/`, `quickstarts/`, plus `examples/` | [Tutorial review](#tutorial-review) β†’ also `work-linear-issue` | +| **Example app only** | `examples/**` without matching MDX | [Example review](#example-review) | +| **Studio ↔ docs links** | `apps/studio/**` | [Studio review](#studio-review) | +| **Docs UI / components** | `apps/docs/components/`, `apps/docs/features/` (no pipeline) | [Component review](#component-review) | +| **Docs tooling** | `.agents/skills/`, `apps/docs/AGENTS.md`, `apps/docs/CONTRIBUTING.md`, `apps/docs/DEVELOPERS.md` | [Docs tooling review](#docs-tooling-review) | +| **Mixed** | Multiple path groups above | Run each applicable section; note overlap | When a PR spans types (e.g. schema handler + component refactor), run **all** matching sections. @@ -131,7 +131,7 @@ Repeat for **each PR** (bottom of stack first). **Checkout and install:** ```bash -cd ~/GitHub/supabase/supabase +cd gh pr checkout --repo supabase/supabase pnpm install --filter docs... # when node_modules missing or deps changed ``` @@ -174,7 +174,7 @@ Find usages: `rg ' # or monorepo equivalent on changed files +pnpm lint:mdx # lints the whole content/ tree; filter the output to your changed paths ``` Checklist: @@ -227,7 +227,7 @@ Tutorial MDX plus matching example app. **Read [`work-linear-issue`](https://git ```bash # MDX lint -cd apps/docs && pnpm lint:mdx -- content/guides/getting-started/tutorials/ +cd apps/docs && pnpm lint:mdx # then check output for content/guides/getting-started/tutorials/ # Example build (from work-linear-issue) cd examples/ @@ -291,7 +291,8 @@ Agent skills, contributor docs, or skill symlink wiring β€” no MDX/pipeline chan Checklist: -- [ ] Symlinks under `.claude/skills/` and `.cursor/skills/` resolve to `.agents/skills/...` (same pattern as `vitest`) +- [ ] `.agents/skills/` is the canonical location β€” no skill content added anywhere else +- [ ] `.claude/skills` is still a single Git symlink to `../.agents/skills` β€” no per-skill symlinks or copies under `.claude/` - [ ] Cross-skill links resolve: relative for in-repo skills; absolute `docs-agent-skills` URLs only for skills that remain in that private repo - [ ] No personal vault paths, Obsidian references, or private-process-only instructions - [ ] `apps/docs/CONTRIBUTING.md` / `DEVELOPERS.md` pointers match skill names and checklist stages @@ -299,7 +300,7 @@ Checklist: ```bash # Symlink smoke check -ls -la .claude/skills/ .cursor/skills/ +test "$(readlink .claude/skills)" = "../.agents/skills" test -f .claude/skills//SKILL.md # Leftover internal refs @@ -317,7 +318,7 @@ One consolidated report after all PRs are reviewed. ```markdown # PR review report β€” -Reviewed locally at `~/GitHub/supabase/supabase`. +Reviewed locally in a `supabase/supabase` checkout. **Stack order:** master β†’ #NNN β†’ … (if applicable) diff --git a/.claude/skills/safe-sql-execution/SKILL.md b/.agents/skills/safe-sql-execution/SKILL.md similarity index 87% rename from .claude/skills/safe-sql-execution/SKILL.md rename to .agents/skills/safe-sql-execution/SKILL.md index 37674ce111403..cec2d5341181e 100644 --- a/.claude/skills/safe-sql-execution/SKILL.md +++ b/.agents/skills/safe-sql-execution/SKILL.md @@ -125,8 +125,8 @@ These are valid ways to generate a `SafeSqlFragment`: - `literal` - `keyword` - Using the safe SQL manipulation utilities: - - `joinSqlFragments` - - `trimSafeSqlFragment` + - `joinSqlFragments` (from `pg-meta`) + - `trimSafeSqlFragment` (from `apps/studio/lib/sql.ts`) `UntrustedSqlFragments` can be generated from raw strings using `untrustedSql()`. @@ -399,32 +399,31 @@ or ClickHouse via the Filter keys and values from URL parameters and UI inputs are spliced into SQL that runs against the project's logs, so the same injection risk exists. -The brand and helpers live in `apps/studio/data/logs/safe-analytics-sql.ts`, -intentionally **disjoint** from the pg-meta `SafeSqlFragment` brand: +Analytics SQL uses its own `SafeLogSqlFragment` brand +(`apps/studio/data/logs/safe-analytics-sql.ts`), intentionally **disjoint** +from the pg-meta `SafeSqlFragment` brand. The brands are kept separate because +escape semantics differ β€” Postgres-safe `E'…'` strings, `::jsonb` casts, and +double-quoted identifiers are unsafe for BigQuery and/or ClickHouse, and vice +versa. Crossing the brands would silently emit unsafe SQL. + +The wire boundary is `executeAnalyticsSql` in +`apps/studio/data/logs/execute-analytics-sql.ts`, analogous to pg-meta's +`executeSql`; it accepts only `SafeLogSqlFragment`, and an eslint +`no-restricted-syntax` rule in `apps/studio/eslint.config.cjs` blocks direct +`post()`/`get()` calls to the `logs.all` endpoints from any other file. + +Build fragments with the helpers in `safe-analytics-sql.ts`: -- `SafeLogSqlFragment` β€” branded type for analytics SQL. - `safeSql` β€” template tag that only accepts `SafeLogSqlFragment` - interpolations. + interpolations; plain strings and Postgres `SafeSqlFragment`s are rejected at + compile time. - `analyticsLiteral(value)` β€” sanitizes string/number/boolean literals. - `quotedIdent(name)` β€” validates and backtick-quotes dotted identifiers. -- `keyword(value, allowed)` β€” validates against an allow-list of operators. +- `keyword(value, allowed)` β€” resolves a value against an allow-list of + fragments (e.g. `AND`/`OR`); never returns the raw input. - `joinSqlFragments(fragments, separator)` β€” composes already-branded fragments. -The brands are kept separate because escape semantics differ β€” Postgres-safe -`E'…'` strings, `::jsonb` casts, and double-quoted identifiers are unsafe for -BigQuery and/or ClickHouse, and vice versa. Crossing the brands would silently -emit unsafe SQL. - -The wire-boundary wrapper is `executeAnalyticsSql` in -`apps/studio/data/logs/execute-analytics-sql.ts`, analogous to pg-meta's -`executeSql`. It accepts only `SafeLogSqlFragment` for its `sql` parameter, so -raw strings are rejected at compile time. A grep-based vitest -(`apps/studio/tests/unit/lints/analytics-sql-boundary.test.ts`) prevents -regressions by failing the build if any file outside -`execute-analytics-sql.ts` calls `post()` or `get()` directly against -`logs.all` or `logs.all.otel`. - ```ts import { executeAnalyticsSql } from '@/data/logs/execute-analytics-sql' import { analyticsLiteral, quotedIdent, safeSql } from '@/data/logs/safe-analytics-sql' @@ -436,13 +435,7 @@ const sql = safeSql` WHERE id = ${analyticsLiteral(id)} ` -await executeAnalyticsSql({ - projectRef, - endpoint: '/platform/projects/{ref}/analytics/endpoints/logs.all', - sql, - iso_timestamp_start, - iso_timestamp_end, -}) +await executeAnalyticsSql({ projectRef, endpoint, sql, iso_timestamp_start, iso_timestamp_end }) ``` ```ts @@ -450,5 +443,20 @@ await executeAnalyticsSql({ // executeAnalyticsSql boundary because the result is `string`, not // `SafeLogSqlFragment`. const sql = `SELECT * FROM ${table} WHERE id = '${id}'` -await executeAnalyticsSql({ projectRef, endpoint, sql, ... }) +await executeAnalyticsSql({ projectRef, endpoint, sql, iso_timestamp_start, iso_timestamp_end }) ``` + +The only path that runs SQL not built from these helpers is user-authored +editor text: `untrustedLogSql(text)` marks it `UntrustedLogSqlFragment` +(displayable and storable, never executable), and `acceptUntrustedLogsSql` +promotes it to `SafeLogSqlFragment`. That promotion is a **security boundary** +β€” call it only from a run gesture (Run button click, Cmd+Enter) or an +approval-gated tool call (the AI notebook tools). Never from render, +`useEffect`, or any automatic path. The notebook persist path also promotes +cells because the writable notebook type requires the safe brand; that is +storage typing, not execution approval, and is not precedent for promoting +anywhere else. The same rule as `acceptUntrustedSql` on the Postgres side. + +Endpoint selection, the OTEL query builders, and the rest of the Studio +wiring live in the `clickhouse-logs-queries` skill +(`references/codebase-integration.md`). diff --git a/.claude/skills/studio-e2e-tests/SKILL.md b/.agents/skills/studio-e2e-tests/SKILL.md similarity index 97% rename from .claude/skills/studio-e2e-tests/SKILL.md rename to .agents/skills/studio-e2e-tests/SKILL.md index ef4cec3fe2924..9e618ae4e781e 100644 --- a/.claude/skills/studio-e2e-tests/SKILL.md +++ b/.agents/skills/studio-e2e-tests/SKILL.md @@ -56,12 +56,6 @@ Wait for elements with generous timeouts: await expect(locator).toBeVisible({ timeout: 30000 }) ``` -Add messages to expects for debugging: - -```typescript -await expect(locator).toBeVisible({ timeout: 30000 }, 'Element should be visible after page load') -``` - Use serial mode for tests sharing database state: ```typescript @@ -213,7 +207,7 @@ await page.waitForLoadState('networkidle') await waitForApiResponse(page, 'pg-meta', ref, 'tables') ``` -Timeouts are acceptable only for client-side debounces: +The only acceptable use of `waitForTimeout` is a client-side debounce: ```ts await page.getByRole('textbox').fill('search term') @@ -222,7 +216,7 @@ await page.waitForTimeout(300) // allow debounce ## Avoiding `waitForTimeout` -Never use `waitForTimeout` - always wait for something specific: +Never use `waitForTimeout` to wait for UI or network β€” always wait for something specific (the debounce case above is the sole exception): ```typescript // BAD diff --git a/.claude/skills/studio-error-handling/SKILL.md b/.agents/skills/studio-error-handling/SKILL.md similarity index 52% rename from .claude/skills/studio-error-handling/SKILL.md rename to .agents/skills/studio-error-handling/SKILL.md index 1cf98d2bcd3cb..313a9f0a3a93f 100644 --- a/.claude/skills/studio-error-handling/SKILL.md +++ b/.agents/skills/studio-error-handling/SKILL.md @@ -30,7 +30,32 @@ handleError() β†’ throws ConnectionTimeoutError β†’ React Query catches β†’ Erro | `TroubleshootingSections.tsx` | Reusable accordion section components | | `TroubleshootingAccordion.tsx` | Accordion wrapper with telemetry | -## Usage +## Which component + +| Situation | Use | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| A query failed and the page/section can't render its data (the default case β€” most of Studio) | `AlertError` from `components/ui/AlertError` | +| The error may be a **classified** type with its own troubleshooting steps (e.g. connection timeout) | `ErrorMatcher` from `components/interfaces/ErrorHandling/ErrorMatcher` β€” pass a `fallback` for the unclassified case | +| A mutation failed | The mutation hook's default `onError` toast (`toast.error` from `sonner`) β€” don't render an alert (see `studio-queries`) | + +### `AlertError` (default) + +Renders a warning `Admonition` with the error message, generic "try refreshing / contact support" instructions, and a **Contact support** button pre-filled with `projectRef`, `subject`, and the error message. + +```tsx +if (isError) return +``` + +- `subject` is the human-readable title, phrased `Failed to `. Pass `projectRef` when in a project context so the support form is pre-filled. +- `error` is the React Query error object (anything with `message`); `503` responses are reworded automatically. +- Use `additionalActions` for a retry or navigate button; `hideContactSupport` only when support genuinely can't help (e.g. a user-input error). +- Prefer the early-return form for the page/section's primary data; use inline `{isError && }` for secondary panels that shouldn't block the rest of the page. + +### `ErrorMatcher` (classified errors) + +Use when the data layer may have classified the error into a `KnownErrorType` with dedicated troubleshooting UI. It reads `errorType` from the error instance and renders the mapped `Troubleshooting` component, or `fallback` when there is no mapping. Today this is wired for the table editor sidebar; reach for it when adding troubleshooting for a new error type rather than as a general replacement for `AlertError`. + +## `ErrorMatcher` usage Pass the **full error object** from React Query β€” not `error.message`: diff --git a/.claude/skills/studio-mock-api-tests/SKILL.md b/.agents/skills/studio-mock-api-tests/SKILL.md similarity index 96% rename from .claude/skills/studio-mock-api-tests/SKILL.md rename to .agents/skills/studio-mock-api-tests/SKILL.md index f073e305750cf..6aab3a8136835 100644 --- a/.claude/skills/studio-mock-api-tests/SKILL.md +++ b/.agents/skills/studio-mock-api-tests/SKILL.md @@ -27,16 +27,18 @@ don't need MSW; render and assert directly. ## The template ```tsx -import { fireEvent, screen, waitFor } from '@testing-library/react' -import userEvent from '@testing-library/user-event' +import { screen } from '@testing-library/react' +import { platformComponents as components } from 'api-types' import { mockAnimationsApi } from 'jsdom-testing-mocks' import { HttpResponse } from 'msw' -import { describe, expect, test, vi } from 'vitest' +import { describe, expect, test } from 'vitest' import { MyComponent } from './MyComponent' import { customRender } from '@/tests/lib/custom-render' import { addAPIMock } from '@/tests/lib/msw' +type OrganizationResponse = components['schemas']['OrganizationResponse'] + // Needed if the component renders inside a Sheet, Modal, Popover, or // anything else built on Radix that uses Web Animations. mockAnimationsApi() @@ -61,7 +63,8 @@ describe('MyComponent', () => { }) ``` -That's the whole pattern. Server lifecycle (`listen`/`resetHandlers`/ +That's the whole pattern (add `fireEvent`, `waitFor`, or `userEvent` as the +interactions need them β€” see the gotchas below). Server lifecycle (`listen`/`resetHandlers`/ `close`) is handled by `apps/studio/tests/vitestSetup.ts` β€” handlers registered via `addAPIMock` are scoped to the current test. diff --git a/.claude/skills/studio-queries/SKILL.md b/.agents/skills/studio-queries/SKILL.md similarity index 99% rename from .claude/skills/studio-queries/SKILL.md rename to .agents/skills/studio-queries/SKILL.md index 528ec033b4444..04241f6155060 100644 --- a/.claude/skills/studio-queries/SKILL.md +++ b/.agents/skills/studio-queries/SKILL.md @@ -111,7 +111,7 @@ const handleClick = useCallback( ```ts import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' -import toast from 'react-hot-toast' +import { toast } from 'sonner' import { xKeys } from './keys' diff --git a/.agents/skills/studio-shortcuts/SKILL.md b/.agents/skills/studio-shortcuts/SKILL.md new file mode 100644 index 0000000000000..772148987ab48 --- /dev/null +++ b/.agents/skills/studio-shortcuts/SKILL.md @@ -0,0 +1,63 @@ +--- +name: studio-shortcuts +description: Keyboard shortcut conventions for Supabase Studio. Use when adding a + repeated user action, toolbar action, list/table operation, or sub-page navigation + that should have shortcut coverage, when registering or changing a shortcut, or when + adding a search/filter Input (which needs the staged-Escape handler). Covers the + shortcut registry, useShortcut, ShortcutTooltip/ShortcutBadge, the reference sheet, + and collision rules. +--- + +# Studio Keyboard Shortcuts + +When Studio UI changes introduce or materially alter repeated user actions, consider whether keyboard shortcut coverage should be added or updated. Shortcuts use the shared Studio shortcut system and must be discoverable from the visible UI. + +## Rules + +- Never add a one-off `keydown` listener for a normal Studio action β€” register it through the shortcut registry and `useShortcut`. +- Every registered shortcut is exposed where the action is visible, via `ShortcutTooltip`, `ShortcutBadge`, or a command-menu badge. +- `G then …` chords are reserved for navigation. +- Avoid broad `Mod+letter` shortcuts that overlap common browser, editor, system, copy/save/search, or devtools behavior. +- Before adding a shortcut, check the registry and any remaining non-registry listeners for collisions. +- Every search/filter `` gets `onKeyDown={onSearchInputEscape(...)}` β€” see [Search inputs](#search-inputs). + +## Preferred pattern + +- Add definitions in `apps/studio/state/shortcuts/registry.ts` or `apps/studio/state/shortcuts/registry/*`. +- Register with `useShortcut`. +- Gate availability with `enabled`. +- Surface visible actions with `ShortcutTooltip` or `ShortcutBadge`. +- Prefer scoped, mnemonic sequential chords over global modifier chords. +- Set `showInSettings: false` on contextual shortcuts (scoped to a specific page state, sheet, or panel). +- When a shortcut group should appear in the reference sheet (`Shift+?`), add the group key to `SHORTCUT_REFERENCE_GROUP_ORDER` in `apps/studio/state/shortcuts/referenceGroups.ts` and a human label to `GROUP_LABELS` in `ShortcutsReferenceSheet.tsx`. +- For sheet-scoped shortcuts (active only while a `` is open), mount `useShortcut` inside the sheet component gated by the `open` prop (`{ enabled: open }`) β€” `apps/studio/components/interfaces/Platform/Webhooks/PlatformWebhooksDeliveryDetailsSheet.tsx` is the canonical example. A shortcut that _opens_ a sheet from anywhere is global instead, gated by whatever makes the action valid (e.g. `useConnectSheetShortcut` checks project health). + +## Search inputs + +Every `` used as a search or filter field must include the staged-Escape handler from `apps/studio/lib/keyboard.ts`: + +```tsx +import { onSearchInputEscape } from '@/lib/keyboard' + +; setQuery(e.target.value)} + onKeyDown={onSearchInputEscape(query, setQuery)} +/> +``` + +Behavior: + +- **Escape while the input has a value** β†’ clears the value, keeps focus (so a second Escape then blurs) +- **Escape while the input is empty** β†’ blurs the input +- Stops propagation on Escape so the keystroke does not accidentally close a parent dialog or sheet + +When pairing with `useShortcut(LIST_PAGE_FOCUS_SEARCH, ...)` to focus a search input via keyboard, always also add `onSearchInputEscape` on the same input β€” focus and escape-to-blur are always a pair. + +## Key files + +`apps/studio/state/shortcuts/registry.ts`, `apps/studio/state/shortcuts/useShortcut.tsx`, `apps/studio/components/ui/Shortcut*.tsx`, `apps/studio/lib/keyboard.ts`. + +## Tests + +E2E tests for a feature with shortcuts cover both click interactions and the keyboard path β€” see `studio-e2e-tests`. diff --git a/.claude/skills/studio-testing/SKILL.md b/.agents/skills/studio-testing/SKILL.md similarity index 88% rename from .claude/skills/studio-testing/SKILL.md rename to .agents/skills/studio-testing/SKILL.md index 39a1720cb0d61..27a8855892e9f 100644 --- a/.claude/skills/studio-testing/SKILL.md +++ b/.agents/skills/studio-testing/SKILL.md @@ -143,14 +143,31 @@ popover open/close with keyboard/mouse, multi-step form transitions. **Not valid:** testing a calculation or transformation that happens to live in a component β€” extract to `.utils.ts` and unit test instead. +Studio component test conventions: + ```tsx -// Studio component test conventions -import { fireEvent } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { customRender } from 'tests/lib/custom-render' // always use customRender, not raw render -import { addAPIMock } from 'tests/lib/msw' // API mocking in beforeEach +import { screen } from '@testing-library/react' +import { platformComponents as components } from 'api-types' +import { HttpResponse } from 'msw' + +import { customRender } from '@/tests/lib/custom-render' +import { addAPIMock } from '@/tests/lib/msw' + +type OrganizationResponse = components['schemas']['OrganizationResponse'] + +addAPIMock({ + method: 'get', + path: '/platform/organizations', + response: () => HttpResponse.json([]), +}) +customRender() +expect(await screen.findByText('No organizations')).toBeInTheDocument() ``` +- Mock API requests at the network layer with `addAPIMock` (MSW) β€” unhandled requests fail the test. Don't `vi.mock('@/data/...')`. Always pass the OpenAPI body type to `HttpResponse.json<…>`. +- `customRender` wraps the component in the providers Studio needs (React Query, router, etc.). +- The full template, path-param syntax, and the jsdom/MSW gotchas are in the `studio-mock-api-tests` skill. + ## 4. E2E Tests for Shared Features (HIGH) If a feature exists in both self-hosted and platform, create an E2E test. diff --git a/.claude/skills/studio-ui-patterns/SKILL.md b/.agents/skills/studio-ui-patterns/SKILL.md similarity index 93% rename from .claude/skills/studio-ui-patterns/SKILL.md rename to .agents/skills/studio-ui-patterns/SKILL.md index 3f8740ec5040b..3af2297df93dc 100644 --- a/.claude/skills/studio-ui-patterns/SKILL.md +++ b/.agents/skills/studio-ui-patterns/SKILL.md @@ -46,7 +46,7 @@ Layout selection: Dirty state / submit: -- Destructure `isDirty` from `form.formState` to show Cancel and disable Save +- Use `isDirty` to show Cancel and disable Save. Destructure it from `form.formState` only in the component that owns `useForm`; anywhere else subscribe with `useFormState({ control })` (see the `react-hook-form` skill) - Show loading on submit button via `loading` prop - If submit button is outside `
`, set a stable `formId` and use `form` prop on the button @@ -76,7 +76,7 @@ Docs: `apps/design-system/content/docs/ui-patterns/charts.mdx` - Use `useChart` context flags for loading/disabled states - Keep composition straightforward β€” avoid over-abstraction -Demos (in `apps/design-system/__registry__/default/block/`): `chart-composed-demo.tsx`, `chart-composed-basic.tsx`, `chart-composed-states.tsx`, `chart-composed-metrics.tsx`, `chart-composed-actions.tsx`, `chart-composed-table.tsx` +Demos (in `apps/design-system/registry/default/block/`): `chart-composed-demo.tsx`, `chart-composed-basic.tsx`, `chart-composed-states.tsx`, `chart-composed-metrics.tsx`, `chart-composed-actions.tsx`, `chart-composed-table.tsx` ## Empty States diff --git a/.claude/skills/telemetry-standards/SKILL.md b/.agents/skills/telemetry-standards/SKILL.md similarity index 70% rename from .claude/skills/telemetry-standards/SKILL.md rename to .agents/skills/telemetry-standards/SKILL.md index 7b06ed799e95b..d79530bb5337b 100644 --- a/.claude/skills/telemetry-standards/SKILL.md +++ b/.agents/skills/telemetry-standards/SKILL.md @@ -69,10 +69,10 @@ enabled, disabled, copied, exposed, failed, converted, closed, completed, applie ## Required Pattern -Import `useTrack` from `lib/telemetry/track` (within `apps/studio/`). Never use `useSendEventMutation` (deprecated). +Import `useTrack` from `@/lib/telemetry/track` (within `apps/studio/`). ```typescript -import { useTrack } from 'lib/telemetry/track' +import { useTrack } from '@/lib/telemetry/track' const MyComponent = () => { const track = useTrack() @@ -89,6 +89,30 @@ const MyComponent = () => { } ``` +## Feature Flag Measurement + +A feature flag that gates behavior needs telemetry on both the flag state and how users respond to the new behavior (toggle clicks, opt-in actions), so the rollout can be measured. + +- **PostHog flags** (`usePHFlag`, or PostHog-backed hooks such as `useDataApiRevokeOnCreateDefaultEnabled`): capture the flag value in a relevant `track()` call. +- **ConfigCat flags** (`useFlag` from `common`) are a different system β€” this pattern does not apply to them. + +`usePHFlag` returns `undefined` while the PostHog store is still loading. Read the raw flag via `usePHFlag('flagName')`, **not** through wrapper hooks that coerce `undefined` to `false`, and use a conditional spread so the property is omitted (not `false`) until the flag has resolved: + +As always, `track()` runs inside the user-action handler β€” never in the component body or an effect: + +```typescript +const track = useTrack() +const flagValue = usePHFlag('myBooleanFlag') // for boolean flags + +const handleSubmit = () => { + track('event_name', { + ...(flagValue !== undefined && { myFlagEnabled: flagValue }), + }) +} +``` + +For string-valued flags (e.g. experiment variants), use `usePHFlag('flagName')`; a flag that may be migrated from boolean to multivariate is typed `usePHFlag`. `ProjectCreationForm.tsx` (`dataApiRevokeOnCreateDefault`) is the canonical example. + ## Event Definitions All events must be defined as TypeScript interfaces in `packages/common/telemetry-constants.ts`: @@ -111,7 +135,7 @@ export interface MyFeatureClickedEvent { ``` Add the new interface to the `TelemetryEvent` union type so `useTrack` picks it up. -`@group Events` and `@source` must be accurate. +`@group Events` and `@source` are required on every event; add `@page` when the event fires from a specific page. All three must be accurate. ## Review Rules @@ -119,9 +143,9 @@ When reviewing a PR, flag these as **required changes:** 1. **Naming violations** β€” event not following `[object]_[verb]` snake_case, or using an unapproved verb 2. **Property violations** β€” not camelCase, generic names, or inconsistent with similar events -3. **Deprecated hook** β€” any usage of `useSendEventMutation` instead of `useTrack` -4. **Unnecessary view tracking** β€” events that fire on page load without user interaction -5. **Inaccurate docs** β€” `@page`/`@source` descriptions that don't match the actual implementation +3. **Unnecessary view tracking** β€” events that fire on page load without user interaction +4. **Inaccurate docs** β€” `@source`/`@page` descriptions that don't match the actual implementation +5. **Unmeasured feature flags** β€” a PostHog flag gates new behavior but its value is not captured in any `track()` call, or there is no outcome tracking for the gated behavior When a PR adds user-facing interactions (buttons, forms, toggles, modals) **without** tracking, suggest: @@ -163,16 +187,16 @@ To add tracking for a user action: 1. **Name the event** β€” `[object]_[verb]` using approved verbs only 2. **Choose properties** β€” camelCase preferred for new events; check `packages/common/telemetry-constants.ts` for similar events and match their property names and casing -3. **Add interface to telemetry-constants.ts** β€” with `@group Events` and `@source` JSDoc, add to the `TelemetryEvent` union type -4. **Add to component** β€” `import { useTrack } from 'lib/telemetry/track'`, call `track('event_name', { properties })` +3. **Add interface to telemetry-constants.ts** β€” with `@group Events` and `@source` JSDoc (plus `@page` when page-specific), add to the `TelemetryEvent` union type +4. **Add to component** β€” `import { useTrack } from '@/lib/telemetry/track'`, call `track('event_name', { properties })` ### Verification checklist - [ ] Event name follows `[object]_[verb]` with approved verb - [ ] Event name is snake_case - [ ] Properties are camelCase and self-explanatory -- [ ] Event defined in telemetry-constants.ts with accurate `@page`/`@source` -- [ ] Using `useTrack` hook (not `useSendEventMutation`) +- [ ] Event defined in telemetry-constants.ts with accurate `@group Events`, `@source`, and (if page-specific) `@page` +- [ ] Using the `useTrack` hook - [ ] Not tracking passive views/appearances - [ ] No PII in event properties (emails, names, IPs, etc.) - [ ] Property names consistent with similar events diff --git a/.claude/skills/vercel-composition-patterns/SKILL.md b/.agents/skills/vercel-composition-patterns/SKILL.md similarity index 97% rename from .claude/skills/vercel-composition-patterns/SKILL.md rename to .agents/skills/vercel-composition-patterns/SKILL.md index afde8507fadcd..e6dbfdbc7dc7a 100644 --- a/.claude/skills/vercel-composition-patterns/SKILL.md +++ b/.agents/skills/vercel-composition-patterns/SKILL.md @@ -85,4 +85,4 @@ Each rule file contains: ## Full Compiled Document -For the complete guide with all rules expanded: `AGENTS.md` +For the complete guide with all rules expanded, read the files under `rules/` diff --git a/.claude/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md b/.agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md similarity index 100% rename from .claude/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md rename to .agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md diff --git a/.claude/skills/vercel-composition-patterns/rules/architecture-compound-components.md b/.agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md similarity index 100% rename from .claude/skills/vercel-composition-patterns/rules/architecture-compound-components.md rename to .agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md diff --git a/.claude/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md b/.agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md similarity index 100% rename from .claude/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md rename to .agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md diff --git a/.claude/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md b/.agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md similarity index 100% rename from .claude/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md rename to .agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md diff --git a/.claude/skills/vercel-composition-patterns/rules/react19-no-forwardref.md b/.agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md similarity index 100% rename from .claude/skills/vercel-composition-patterns/rules/react19-no-forwardref.md rename to .agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md diff --git a/.claude/skills/vercel-composition-patterns/rules/state-context-interface.md b/.agents/skills/vercel-composition-patterns/rules/state-context-interface.md similarity index 100% rename from .claude/skills/vercel-composition-patterns/rules/state-context-interface.md rename to .agents/skills/vercel-composition-patterns/rules/state-context-interface.md diff --git a/.claude/skills/vercel-composition-patterns/rules/state-decouple-implementation.md b/.agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md similarity index 100% rename from .claude/skills/vercel-composition-patterns/rules/state-decouple-implementation.md rename to .agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md diff --git a/.claude/skills/vercel-composition-patterns/rules/state-lift-state.md b/.agents/skills/vercel-composition-patterns/rules/state-lift-state.md similarity index 100% rename from .claude/skills/vercel-composition-patterns/rules/state-lift-state.md rename to .agents/skills/vercel-composition-patterns/rules/state-lift-state.md diff --git a/.agents/skills/write-the-docs/SKILL.md b/.agents/skills/write-the-docs/SKILL.md index 989d7a61fbb8b..8380bd4c03e3c 100644 --- a/.agents/skills/write-the-docs/SKILL.md +++ b/.agents/skills/write-the-docs/SKILL.md @@ -50,7 +50,7 @@ When in doubt, ask `ask-the-docs` rather than guessing β€” this classification i - **Wire it into navigation, not just onto disk.** Placement (which section) and nav enablement (whether it actually shows up) are separate β€” confirm the current nav-registration mechanism via `ask-the-docs`/`audit-docs-ia` rather than assuming a page is discoverable just because the file exists in the right folder. - Ground every behavior claim in Phase 1's code read (the linked PR when there is one); ground every "why this matters" framing in Linear/PM context or prior Frame/Shape output; mark inferred material inline (e.g. an HTML comment or a flagged line in the handoff summary) so a reviewer can find it fast. - **Write for timelessness.** Prefer documenting what exists now over promising future features. See [reference/common-pitfalls.md](reference/common-pitfalls.md#2-timeless-documentation). -- **Keep it concise and avoid redundancy.** See [reference/common-pitfalls.md](reference/common-pitfalls.md#4-redundancy). +- **Keep it concise and avoid redundancy.** See [reference/common-pitfalls.md](reference/common-pitfalls.md#4-redundancy-and-over-explanation). - **Prefer paragraphs over single-item lists.** See [reference/common-pitfalls.md](reference/common-pitfalls.md#5-single-item-lists). - **Strip internal business context before the final draft.** HTML comments flagging PRD intent, roadmap speculation, internal ticket discussions, or "gap-fill" notes must be removed from MDX before handoff. Open-source docs shouldn't expose internal planning. Flag assumptions and open questions for reviewers in the PR description instead, not in the shipped content. - Search [`apps/docs/WORD_LIST.md`](../../../apps/docs/WORD_LIST.md) when introducing or reviewing technical terms, UI actions, abbreviations, and potentially ambiguous language during drafting. This targeted search supplements, but does not replace, the full-file compliance check in Phase 2.5. diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000000000..2b7a412b8fa0f --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.claude/skills/ask-the-docs b/.claude/skills/ask-the-docs deleted file mode 120000 index d71a2076e4f03..0000000000000 --- a/.claude/skills/ask-the-docs +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/ask-the-docs \ No newline at end of file diff --git a/.claude/skills/edit-the-docs b/.claude/skills/edit-the-docs deleted file mode 120000 index 474cc6eaf3a62..0000000000000 --- a/.claude/skills/edit-the-docs +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/edit-the-docs \ No newline at end of file diff --git a/.claude/skills/pm-the-docs b/.claude/skills/pm-the-docs deleted file mode 120000 index b2cc2ea4b4709..0000000000000 --- a/.claude/skills/pm-the-docs +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/pm-the-docs \ No newline at end of file diff --git a/.claude/skills/review-the-docs b/.claude/skills/review-the-docs deleted file mode 120000 index 49fccdd463dab..0000000000000 --- a/.claude/skills/review-the-docs +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/review-the-docs \ No newline at end of file diff --git a/.claude/skills/vercel-composition-patterns/AGENTS.md b/.claude/skills/vercel-composition-patterns/AGENTS.md deleted file mode 100644 index 558bf9aa1e36d..0000000000000 --- a/.claude/skills/vercel-composition-patterns/AGENTS.md +++ /dev/null @@ -1,946 +0,0 @@ -# React Composition Patterns - -**Version 1.0.0** -Engineering -January 2026 - -> **Note:** -> This document is mainly for agents and LLMs to follow when maintaining, -> generating, or refactoring React codebases using composition. Humans -> may also find it useful, but guidance here is optimized for automation -> and consistency by AI-assisted workflows. - ---- - -## Abstract - -Composition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier for both humans and AI agents to work with as they scale. - ---- - -## Table of Contents - -1. [Component Architecture](#1-component-architecture) β€” **HIGH** - - 1.1 [Avoid Boolean Prop Proliferation](#11-avoid-boolean-prop-proliferation) - - 1.2 [Use Compound Components](#12-use-compound-components) -2. [State Management](#2-state-management) β€” **MEDIUM** - - 2.1 [Decouple State Management from UI](#21-decouple-state-management-from-ui) - - 2.2 [Define Generic Context Interfaces for Dependency Injection](#22-define-generic-context-interfaces-for-dependency-injection) - - 2.3 [Lift State into Provider Components](#23-lift-state-into-provider-components) -3. [Implementation Patterns](#3-implementation-patterns) β€” **MEDIUM** - - 3.1 [Create Explicit Component Variants](#31-create-explicit-component-variants) - - 3.2 [Prefer Composing Children Over Render Props](#32-prefer-composing-children-over-render-props) -4. [React 19 APIs](#4-react-19-apis) β€” **MEDIUM** - - 4.1 [React 19 API Changes](#41-react-19-api-changes) - ---- - -## 1. Component Architecture - -**Impact: HIGH** - -Fundamental patterns for structuring components to avoid prop -proliferation and enable flexible composition. - -### 1.1 Avoid Boolean Prop Proliferation - -**Impact: CRITICAL (prevents unmaintainable component variants)** - -Don't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize - -component behavior. Each boolean doubles possible states and creates - -unmaintainable conditional logic. Use composition instead. - -**Incorrect: boolean props create exponential complexity** - -```tsx -function Composer({ - onSubmit, - isThread, - channelId, - isDMThread, - dmId, - isEditing, - isForwarding, -}: Props) { - return ( - -
- - {isDMThread ? ( - - ) : isThread ? ( - - ) : null} - {isEditing ? ( - - ) : isForwarding ? ( - - ) : ( - - )} -