diff --git a/.github/workflows/studio-guidelines-sync.yml b/.github/workflows/studio-guidelines-sync.yml
new file mode 100644
index 00000000000..388b527b6ab
--- /dev/null
+++ b/.github/workflows/studio-guidelines-sync.yml
@@ -0,0 +1,39 @@
+name: Studio Guidelines Sync
+
+on:
+ pull_request:
+ branches:
+ - 'master'
+ paths:
+ - '.claude/skills/**'
+ - 'apps/studio/AGENTS.md'
+ - 'scripts/sync-studio-guidelines.mjs'
+
+# Cancel old builds on new commit for same workflow + branch/PR
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ check:
+ name: Check apps/studio/AGENTS.md is in sync
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - name: Check out repo
+ uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ with:
+ persist-credentials: false
+ sparse-checkout: |
+ .claude/skills
+ apps/studio/AGENTS.md
+ scripts
+ .nvmrc
+ - name: Setup node
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version-file: '.nvmrc'
+ - name: Verify guidelines are up to date
+ run: node scripts/sync-studio-guidelines.mjs --check
diff --git a/.prettierignore b/.prettierignore
index 66e4d58fa92..f92e507b1b8 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -21,6 +21,8 @@ apps/studio/public
apps/**/.turbo
apps/docs/CONTRIBUTING.md
apps/docs/__generated__
+# Generated by scripts/sync-studio-guidelines.mjs from .claude/skills/
+apps/studio/AGENTS.md
apps/design-system/__registry__
# TanStack Router auto-generated route tree; the file header explicitly
# says to exclude it from formatters.
diff --git a/apps/studio/AGENTS.md b/apps/studio/AGENTS.md
new file mode 100644
index 00000000000..bfd4bbe52ad
--- /dev/null
+++ b/apps/studio/AGENTS.md
@@ -0,0 +1,585 @@
+
+
+# Studio Code Review Guidelines
+
+Conventions for `apps/studio` code, compiled from the Studio skills. Apply these
+when reviewing changes under `apps/studio/`.
+
+
+
+# Studio Best Practices
+
+Applies to `apps/studio/**/*.{ts,tsx}`.
+
+## Boolean Naming
+
+Use descriptive prefixes — derive from existing state rather than storing separately:
+
+- `is` — state/identity: `isLoading`, `isPaused`, `isNewRecord`
+- `has` — possession: `hasPermission`, `hasData`
+- `can` — capability: `canUpdateColumns`, `canDelete`
+- `should` — conditional behavior: `shouldFetch`, `shouldRender`
+
+Extract complex conditions into named variables:
+
+```tsx
+// ❌ inline multi-condition
+{
+ !isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading &&
+}
+
+// ✅ named variable
+const canShowAddButton =
+ !isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading
+{
+ canShowAddButton &&
+}
+```
+
+Derive booleans — don't store them:
+
+```tsx
+// ❌ stored derived state
+const [isFormValid, setIsFormValid] = useState(false)
+useEffect(() => {
+ setIsFormValid(name.length > 0 && email.includes('@'))
+}, [name, email])
+
+// ✅ derived
+const isFormValid = name.length > 0 && email.includes('@')
+```
+
+## Component Structure
+
+See `vercel-composition-patterns` skill for compound component and composition patterns.
+
+Keep components under 200–300 lines. Split when you see:
+
+- Multiple distinct UI sections
+- Complex conditional rendering
+- Multiple unrelated `useState` calls
+- Hard to understand at a glance
+
+Co-locate sub-components in the same directory as the parent. Avoid barrel re-export files.
+
+Extract repeated JSX patterns into small components.
+
+## Data Fetching
+
+All data fetching uses TanStack Query (React Query). See `studio-queries` skill for query/mutation patterns and `studio-error-handling` skill for error display conventions.
+
+### Loading / Error / Success Pattern
+
+Top level:
+
+```tsx
+const { data, error, isLoading, isError, isSuccess } = useQuery(...)
+
+if (isLoading) return
+if (isError) return
+if (isSuccess && data.length === 0) return
+return
+```
+
+Use early returns — avoid deeply nested conditionals.
+
+Inline:
+
+```tsx
+
+ {isLoading && }
+ {isError && }
+ {isSuccess && data.length === 0 && }
+ {isSuccess && data.length > 0 && }
+
+```
+
+## State Management
+
+Keep state as local as possible; lift only when needed.
+
+Group related form state with `react-hook-form` rather than multiple `useState` calls. See `studio-ui-patterns` skill for form layout and component conventions.
+
+```tsx
+// ❌ multiple related useState
+const [name, setName] = useState('')
+const [email, setEmail] = useState('')
+
+// ✅ grouped with react-hook-form
+const form = useForm({ defaultValues: { name: '', email: '' } })
+```
+
+## Custom Hooks
+
+Extract complex or reusable logic into hooks. Return objects, not arrays:
+
+```tsx
+// ❌ array return (hard to extend)
+return [value, toggle]
+
+// ✅ object return
+return { value, toggle, setTrue, setFalse }
+```
+
+## Event Handlers
+
+- Prop callbacks: `on` prefix (`onClose`, `onSave`)
+- Internal handlers: `handle` prefix (`handleSubmit`, `handleCancel`)
+
+Use `useCallback` for handlers passed to memoized children; avoid unnecessary inline arrow functions.
+
+## Conditional Rendering
+
+```tsx
+// Simple show/hide
+<>{isVisible && }>
+
+// Binary choice
+<>{isLoading ? : }>
+
+// Multiple conditions — use early returns, not nested ternaries
+if (isLoading) return
+if (isError) return
+return
+```
+
+## Performance
+
+`useMemo` for genuinely expensive computations (measured, not assumed). Don't wrap everything — only optimize when you have a measured problem or are passing values to memoized children.
+
+## TypeScript
+
+Define prop interfaces explicitly. Use discriminated unions for complex state:
+
+```tsx
+type AsyncState =
+ | { status: 'idle' }
+ | { status: 'loading' }
+ | { status: 'success'; data: T }
+ | { status: 'error'; error: Error }
+```
+
+Avoid `as any` / `as Type` casts. Validate at boundaries with zod:
+
+```tsx
+// ❌ type cast
+const user = apiResponse as User
+
+// ✅ zod parse
+const user = userSchema.parse(apiResponse)
+// or safe:
+const result = userSchema.safeParse(apiResponse)
+```
+
+## Testing
+
+Extract logic into `.utils.ts` pure functions and test exhaustively. See the `studio-testing` skill for the full testing strategy and decision tree.
+
+---
+
+
+
+# Studio UI Patterns
+
+The Design System docs and demos are the source of truth. Always check the relevant
+demo file before composing new UI.
+
+## Layout
+
+Docs: `apps/design-system/content/docs/ui-patterns/layout.mdx`
+
+Build pages with `PageContainer`, `PageHeader`, and `PageSection`.
+
+| Content type | `size` |
+| ----------------- | ----------- |
+| Settings / config | `"default"` |
+| Lists / tables | `"large"` |
+| Full-screen views | `"full"` |
+
+- If filters/search exist on a list page, align table actions with the filters (don't use `PageHeaderAside`/`PageSectionAside` for those actions)
+- If no filters, actions can go in `PageHeaderAside` or `PageSectionAside`
+
+Demos: `page-layout-settings.tsx`, `page-layout-list.tsx`, `page-layout-list-simple.tsx`, `page-layout-detail.tsx`
+(all in `apps/design-system/registry/default/example/`)
+
+## Forms
+
+Docs: `apps/design-system/content/docs/ui-patterns/forms.mdx`
+
+- Use `react-hook-form` + `zod`
+- Use `FormItemLayout` instead of manually composing `FormItem`/`FormLabel`/`FormMessage`/`FormDescription`
+- Wrap inputs with `FormControl`; use `_Shadcn_` imports from `ui` for primitives
+
+Layout selection:
+
+| Context | Layout | Container |
+| ------------------------------------------ | ------------------------------------------ | ---------------------------------------------------------- |
+| Page (settings/config) | `FormItemLayout layout="flex-row-reverse"` | `Card` (`CardContent` per field; `CardFooter` for actions) |
+| Side panel — wide | `FormItemLayout layout="horizontal"` | `SheetSection` |
+| Side panel — narrow (`size="sm"` or below) | `FormItemLayout layout="vertical"` | `SheetSection` |
+
+Dirty state / submit:
+
+- Destructure `isDirty` from `form.formState` to show Cancel and disable Save
+- Show loading on submit button via `loading` prop
+- If submit button is outside `