diff --git a/.agents/skills/vitest/SKILL.md b/.agents/skills/vitest/SKILL.md index 0578bdcf3a8..69776b6edad 100644 --- a/.agents/skills/vitest/SKILL.md +++ b/.agents/skills/vitest/SKILL.md @@ -1,15 +1,21 @@ --- name: vitest -description: Vitest fast unit testing framework powered by Vite with Jest-compatible API. Use when writing tests, mocking, configuring coverage, or working with test filtering and fixtures. +description: >- + Vitest API and config reference (Jest-compatible) — mocking with vi.*, spies, + fake timers, coverage configuration, fixtures, snapshots, and test filtering. + Use for Vitest API and configuration questions anywhere in the monorepo; for + Studio-specific test strategy and component-test setup, start with + studio-testing and studio-mock-api-tests. metadata: author: Anthony Fu - version: "2026.1.28" + version: '2026.1.28' source: Generated from https://github.com/vitest-dev/vitest, scripts located at https://github.com/antfu/skills --- Vitest is a next-generation testing framework powered by Vite. It provides a Jest-compatible API with native ESM, TypeScript, and JSX support out of the box. Vitest shares the same config, transformers, resolvers, and plugins with your Vite app. **Key Features:** + - Vite-native: Uses Vite's transformation pipeline for fast HMR-like test updates - Jest-compatible: Drop-in replacement for most Jest test suites - Smart watch mode: Only reruns affected tests based on module graph @@ -22,31 +28,31 @@ Vitest is a next-generation testing framework powered by Vite. It provides a Jes ## Core -| Topic | Description | Reference | -|-------|-------------|-----------| -| Configuration | Vitest and Vite config integration, defineConfig usage | [core-config](references/core-config.md) | -| CLI | Command line interface, commands and options | [core-cli](references/core-cli.md) | -| Test API | test/it function, modifiers like skip, only, concurrent | [core-test-api](references/core-test-api.md) | -| Describe API | describe/suite for grouping tests and nested suites | [core-describe](references/core-describe.md) | -| Expect API | Assertions with toBe, toEqual, matchers and asymmetric matchers | [core-expect](references/core-expect.md) | -| Hooks | beforeEach, afterEach, beforeAll, afterAll, aroundEach | [core-hooks](references/core-hooks.md) | +| Topic | Description | Reference | +| ------------- | --------------------------------------------------------------- | -------------------------------------------- | +| Configuration | Vitest and Vite config integration, defineConfig usage | [core-config](references/core-config.md) | +| CLI | Command line interface, commands and options | [core-cli](references/core-cli.md) | +| Test API | test/it function, modifiers like skip, only, concurrent | [core-test-api](references/core-test-api.md) | +| Describe API | describe/suite for grouping tests and nested suites | [core-describe](references/core-describe.md) | +| Expect API | Assertions with toBe, toEqual, matchers and asymmetric matchers | [core-expect](references/core-expect.md) | +| Hooks | beforeEach, afterEach, beforeAll, afterAll, aroundEach | [core-hooks](references/core-hooks.md) | ## Features -| Topic | Description | Reference | -|-------|-------------|-----------| -| Mocking | Mock functions, modules, timers, dates with vi utilities | [features-mocking](references/features-mocking.md) | -| Snapshots | Snapshot testing with toMatchSnapshot and inline snapshots | [features-snapshots](references/features-snapshots.md) | -| Coverage | Code coverage with V8 or Istanbul providers | [features-coverage](references/features-coverage.md) | -| Test Context | Test fixtures, context.expect, test.extend for custom fixtures | [features-context](references/features-context.md) | -| Concurrency | Concurrent tests, parallel execution, sharding | [features-concurrency](references/features-concurrency.md) | -| Filtering | Filter tests by name, file patterns, tags | [features-filtering](references/features-filtering.md) | +| Topic | Description | Reference | +| ------------ | -------------------------------------------------------------- | ---------------------------------------------------------- | +| Mocking | Mock functions, modules, timers, dates with vi utilities | [features-mocking](references/features-mocking.md) | +| Snapshots | Snapshot testing with toMatchSnapshot and inline snapshots | [features-snapshots](references/features-snapshots.md) | +| Coverage | Code coverage with V8 or Istanbul providers | [features-coverage](references/features-coverage.md) | +| Test Context | Test fixtures, context.expect, test.extend for custom fixtures | [features-context](references/features-context.md) | +| Concurrency | Concurrent tests, parallel execution, sharding | [features-concurrency](references/features-concurrency.md) | +| Filtering | Filter tests by name, file patterns, tags | [features-filtering](references/features-filtering.md) | ## Advanced -| Topic | Description | Reference | -|-------|-------------|-----------| -| Vi Utilities | vi helper: mock, spyOn, fake timers, hoisted, waitFor | [advanced-vi](references/advanced-vi.md) | -| Environments | Test environments: node, jsdom, happy-dom, custom | [advanced-environments](references/advanced-environments.md) | -| Type Testing | Type-level testing with expectTypeOf and assertType | [advanced-type-testing](references/advanced-type-testing.md) | -| Projects | Multi-project workspaces, different configs per project | [advanced-projects](references/advanced-projects.md) | +| Topic | Description | Reference | +| ------------ | ------------------------------------------------------- | ------------------------------------------------------------ | +| Vi Utilities | vi helper: mock, spyOn, fake timers, hoisted, waitFor | [advanced-vi](references/advanced-vi.md) | +| Environments | Test environments: node, jsdom, happy-dom, custom | [advanced-environments](references/advanced-environments.md) | +| Type Testing | Type-level testing with expectTypeOf and assertType | [advanced-type-testing](references/advanced-type-testing.md) | +| Projects | Multi-project workspaces, different configs per project | [advanced-projects](references/advanced-projects.md) | diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 175aec83c51..8f48264e86e 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -24,7 +24,6 @@ pnpm 11 + Turborepo monorepo. Requires Node >= 22.13. ## Common Commands ```bash -pnpm install # install dependencies pnpm dev:studio # run Studio dev server → http://localhost:8082 pnpm dev:docs # run docs dev server pnpm dev:www # run www dev server @@ -63,6 +62,7 @@ The skills in `.claude/skills/` are the source of truth for conventions — load - `telemetry-standards` — PostHog events, `packages/common/telemetry-constants.ts` - `dev-toolbar-review` — `packages/dev-tools`, `packages/common/posthog-client.ts`, `packages/common/feature-flags.tsx` - `safe-sql-execution` — any code that builds or executes SQL against user databases +- `react-hook-form` — writing or modifying any form code, anywhere in the monorepo - `vitest` / `vercel-composition-patterns` — generic unit-testing and React composition references ## Studio diff --git a/.claude/settings.json b/.claude/settings.json index 3ffd1f88913..66fdfd3e7e5 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,4 +1,14 @@ { + "permissions": { + "deny": [ + "Edit(packages/api-types/types/**)", + "Edit(**/routeTree.gen.ts)", + "Edit(**/__generated__/**)", + "Edit(apps/docs/features/docs/generated/**)", + "Edit(apps/www/.generated/**)", + "Edit(supabase/functions/common/database-types.ts)" + ] + }, "hooks": { "SessionStart": [ { diff --git a/.claude/skills/copywriting/SKILL.md b/.claude/skills/copywriting/SKILL.md index 1a6994d8020..f69a7e0ed90 100644 --- a/.claude/skills/copywriting/SKILL.md +++ b/.claude/skills/copywriting/SKILL.md @@ -1,6 +1,6 @@ --- name: copywriting -description: Write or audit UI copy (buttons, labels, empty states, error messages, tooltips, form text) anywhere in the monorepo. Always check this before shipping or reviewing user-facing text. +description: Write or audit UI copy (buttons, labels, empty states, error messages, tooltips, form text) anywhere in the monorepo. Load it before shipping or reviewing any user-facing text — including when copy is incidental to the task, like a new feature that adds buttons, toasts, dialogs, or validation messages. --- # Copywriting diff --git a/.claude/skills/dev-toolbar-review/SKILL.md b/.claude/skills/dev-toolbar-review/SKILL.md index 8ead0b413f4..ae6b81d10ab 100644 --- a/.claude/skills/dev-toolbar-review/SKILL.md +++ b/.claude/skills/dev-toolbar-review/SKILL.md @@ -1,6 +1,7 @@ --- name: dev-toolbar-review -description: Use when reviewing PRs that touch packages/dev-tools/, packages/common/posthog-client.ts, +description: Safety rules for the dev toolbar, PostHog client, and feature flags. Use + when writing or reviewing any change to packages/dev-tools/, packages/common/posthog-client.ts, or packages/common/feature-flags.tsx. Covers environment guards, flag override cookies, telemetry event subscription, and SSE stream safety. --- @@ -30,10 +31,12 @@ so PRs touching only those files won't auto-request review. Watch for these in t **Files:** `packages/dev-tools/index.ts`, `DevToolbar.tsx`, `DevToolbarTrigger.tsx`, `DevToolbarContext.tsx` 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. **Check for:** + - Guards being removed or broadened. The toolbar is expanding to staging and preview deploys but must remain invisible in production. - 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. @@ -43,14 +46,17 @@ The toolbar uses two layers of protection: **Files:** `packages/dev-tools/DevToolbar.tsx`, `packages/common/posthog-client.ts`, `packages/common/feature-flags.tsx` The toolbar writes two cookies that override feature flags locally: + - `x-ph-flag-overrides` — PostHog flag overrides - `x-cc-flag-overrides` — ConfigCat flag overrides These are read by: + - `posthog-client.ts:getFeatureFlag()` — checks the PostHog override cookie before querying the SDK - `feature-flags.tsx` — merges both override cookies into the flag store during initialization **Check for:** + - Cookie name changes (must stay in sync across writer and all readers) - Changes to the merge/precedence logic in `feature-flags.tsx` (currently: `vercel-flag-overrides` first, then `x-cc-flag-overrides` takes precedence in local dev) - Override cookies being read outside the `IS_LOCAL_DEV` / `isLocalDev` guard — overrides must never affect production flag evaluation @@ -66,6 +72,7 @@ and `identify`. Note: `captureExperimentExposure` calls `posthog.capture()` dire without emitting to dev listeners — experiment exposure events are invisible in the toolbar. **Check for:** + - Changes to `emitToDevListeners` or `subscribeToEvents` that could introduce side effects on the actual capture path (e.g., throwing errors, blocking, mutating event data) - The listener set (`devListeners`) being iterated synchronously in a way that could delay event dispatch - New PostHog client methods that capture events but don't call `emitToDevListeners` (gap in toolbar visibility) @@ -78,6 +85,7 @@ The toolbar connects to `${apiUrl}/telemetry/stream` via Server-Sent Events to d server-side telemetry. Uses exponential backoff on connection errors. **Check for:** + - Changes to the SSE endpoint URL or `session_id` cookie handling - Reconnection logic changes that could cause excessive retries or connection leaks - Note: the stream endpoint lives in the platform repo — cross-repo changes need coordinated review @@ -85,16 +93,19 @@ server-side telemetry. Uses exponential backoff on connection errors. ### 5. App-Level Mounting **Provider + toolbar panel** (`DevToolbarProvider`, `DevToolbar`): + - `apps/studio/pages/_app.tsx` - `apps/www/pages/_app.tsx`, `apps/www/app/providers.tsx` - `apps/docs/features/app.providers.tsx` **Trigger button** (`DevToolbarTrigger`) — rendered separately in nav/header components: + - `apps/studio/components/layouts/Navigation/LayoutHeader/LayoutHeader.tsx` - `apps/www/components/Nav/index.tsx` - `apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx` **Check for:** + - Provider being added or removed from an app - `apiUrl` prop changes (must point to the correct platform API) - Rendering order changes that could affect the toolbar's access to PostHog context diff --git a/.claude/skills/react-hook-form/SKILL.md b/.claude/skills/react-hook-form/SKILL.md new file mode 100644 index 00000000000..29571cb24c7 --- /dev/null +++ b/.claude/skills/react-hook-form/SKILL.md @@ -0,0 +1,278 @@ +--- +name: react-hook-form +description: Correct React Hook Form usage anywhere in the monorepo — data flow, subscriptions, + reset, dirty state, number inputs, and controlled-input rules. Load this BEFORE + writing or modifying ANY form code, adding a field to an existing form, touching + watch/useWatch/formState/getValues/setValue/reset, wiring a form into a dialog or + sheet, or building a submit/cancel footer — even when the change looks trivial. + The codebase contains widespread RHF anti-patterns; without this skill you will + copy them. For form layout and which components to use, also load + studio-ui-patterns. +--- + +# React Hook Form + +How to write forms that stay correct as they grow. The existing codebase is **not** +a safe reference: `form.watch()` off prop-drilled form objects, subscription-only +watches, unguarded `valueAsNumber`, and `?? undefined` controlled values are all +common in older code and all wrong. Follow this skill, not the neighboring file. + +**Policy — fix what you touch.** New code must follow these rules. When you modify +existing form code, upgrade the specific fields/hooks/components you're editing to +match (e.g. a component you touch that calls `form.watch` gets converted to +`useWatch`). Leave untouched code alone, but tell the user about anti-patterns you +noticed and didn't fix. Never add new violations: `react-hook-form/no-use-watch` +is ratcheted in Studio CI — any increase in the warning count fails the build. + +## Mental model: subscriptions decide who re-renders + +RHF is uncontrolled at heart. Values live in refs; nothing re-renders unless a +subscription says so. Every read API is a subscription decision: + +| API | Subscribes | Re-renders | Use for | +| ----------------------------- | ---------- | -------------------------- | ---------------------------------------------- | +| `useWatch({ control, name })` | yes | only the calling component | reactive value reads, anywhere | +| `useFormState({ control })` | yes | only the calling component | `isDirty`/`errors`/etc. outside the form owner | +| `formState` (destructured) | yes | the `useForm` owner | form state **in the owner component only** | +| `form.watch(name)` | yes | the **entire form tree** | avoid — lint-flagged, see below | +| `getValues()` | no | never | event handlers and `onSubmit` only | +| `subscribe()` | callback | none | side effects outside render | + +Two facts explain most of the bugs we've shipped: + +1. **`form.watch()` and `form.formState` hoist their subscription to the `useForm` + owner**, no matter which component calls them. A child that reads + `form.watch('x')` off a prop works today only because the whole tree re-renders + on every change — it silently goes stale the moment anyone adds `React.memo` + between owner and child, and until then it re-renders every sibling on every + keystroke. A no-arg `form.watch()` sets `watchAll` and re-renders the tree on + every field change for the life of the form. +2. **`formState` is a Proxy** — reading a property is what arms the subscription. + Destructure it (`const { isDirty } = form.formState`), never pass the object + around or read it conditionally (`a && formState.isValid` may never subscribe). + Enforced by `react-hook-form/destructuring-formstate` (error). + +### Reading values, by location + +- **In the component that owns `useForm`:** destructure `formState`; prefer + `useWatch` over `form.watch` even here (the `no-use-watch` rule flags every + `watch`, and `useWatch` scopes the re-render if the JSX is later extracted). +- **In any child component or custom hook:** accept `control` (not the whole + `form`) and use `useWatch({ control, name })` / `useFormState({ control })`. + Inside `