## Summary
- Follow-up to the axe-core accessibility audit (FE-3781), which found
1,733 failing elements across 126 Studio surfaces deduplicating to 12
root-cause families. A subset of those (missing accessible names/labels,
invalid/redundant ARIA, empty headings/anchors) is statically detectable
— this adds ESLint coverage for it instead of relying solely on the
runtime axe-core CI gate.
- Adds 13 `jsx-a11y` rules to `apps/studio/eslint.config.cjs` at
`'warn'`: `aria-props`, `aria-proptypes`, `role-supports-aria-props`,
`anchor-has-content`, `control-has-associated-label`
(`controlComponents: ['Button', 'Switch']`),
`label-has-associated-control` (`labelComponents: ['Label']`,
`controlComponents: ['Input', 'Switch']`), `aria-role`,
`no-redundant-roles`, `no-aria-hidden-on-focusable`,
`tabindex-no-positive`, `anchor-is-valid`, `heading-has-content`,
`no-distracting-elements`.
- Wires all 13 into the existing `lint:ratchet` script and initializes
their baselines in `apps/studio/.github/eslint-rule-baselines.json`, so
any *new* violation fails `studio-lint-ratchet.yml` while the
pre-existing ones (mostly `control-has-associated-label`: 274,
`label-has-associated-control`: 37) are tracked and shrink over time via
the weekly baseline-decrease cron.
Resolves
[FE-3795](https://linear.app/supabase/issue/FE-3795/add-jsx-a11y-eslint-ratchet-rules-for-statically-detectable-a11y).
## Test plan
- [x] `pnpm --filter studio run lint:ratchet` passes (exit 0, no
regressions)
- [x] Spot-checked several flagged instances against source to confirm
true positives (e.g. an unlabeled save/cancel icon-button pair in
`AIAssistantChatSelector.tsx`, an empty `<h3>` in `PITRForm.tsx`)
- [x] CI (`studio-lint-ratchet.yml`, typecheck.yml lint step) green on
this PR
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Expanded Studio’s accessibility linting to cover additional ARIA prop
validation, label/control relationships, anchor/heading validity,
role/ARIA correctness, and focus/tab behavior (including distracting
markup).
* Updated accessibility lint baselines so tracked violations remain
accurate as rules expand.
* **New Features**
* Enhanced the Studio lint “ratchet” workflow to load ratchet rule IDs
from an external `rules-file` instead of a long inline command.
* **Tests**
* Added an integration test to verify rule IDs are read from the
`rules-file`.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Extracted from the TanStack Start migration (#46424) to shrink that PR.
The self-hosted storage/auth API routes each constructed a module-scope
admin client (`createClient(process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!)`). Those env vars only exist on
self-hosted, so eager module-scope construction is wasteful on platform
and fragile on any runtime that evaluates an API module before its route
is hit (constructing with `undefined` credentials throws on import).
**Changed:**
- Add `lib/api/self-hosted-admin.ts` — `selfHostedSupabaseAdmin`, a
`Proxy` that defers `createClient(...)` until first property access
(inside a handler, i.e. on self-hosted where the vars are set).
- Swap **all 17** storage/auth/vector-bucket handlers from module-scope
`createClient(...)` to `import { selfHostedSupabaseAdmin as supabase }`.
- **Enforce it:** add an eslint `no-restricted-syntax` rule banning
module-scope `createClient` in `pages/api/**` + `routes/**` (now that
every flagged handler is lazy). The same eslint config block also
carries an analytics-SQL boundary rule — 0 violations on master.
Behaviour is unchanged (the client is still built lazily inside the
handler). This is also the change that makes those routes safe under
TanStack's single-handler module evaluation.
## To test
- Self-hosted Studio: storage buckets/objects, vector buckets, and auth
users operations work as before.
## Verification
studio lint (0 errors, both rules active) ✓ · studio typecheck ✓.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Standardized self-hosted Supabase admin client usage across platform
authentication and storage endpoints, removing per-route client setup.
* Improved reliability by lazily creating the admin client only when
first used.
* **Chores / Tooling**
* Updated ESLint rules to prevent module-scope Supabase client creation
in API routes and to enforce safe analytics SQL access patterns.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
Co-authored-by: Ali Waseem <waseema393@gmail.com>
## 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?
Refactor / chore — lints the analytics SQL wire boundary and tightens
internal API surface. Final PR in the safe-analytics-sql series (stacked
on #46476).
## What is the current behavior?
After PRs 1–10, every analytics SQL call site routes through
`executeAnalyticsSql`, but nothing prevents a future caller from
regressing by calling
`post('/platform/projects/{ref}/analytics/endpoints/logs.all', …)`
directly. `safe-analytics-sql.ts` also exports `rawSql` and
`LogSqlFragmentSeparator`, neither of which has external consumers —
`rawSql` in particular is a cast-to-brand escape hatch that should not
be reachable from outside the file. The safe-sql-execution skill
documents only the pg-meta (Postgres) side of the model.
## What is the new behavior?
- Adds an ESLint `no-restricted-syntax` rule in
`apps/studio/eslint.config.cjs` that fails on direct `post()` / `get()`
calls against
`/platform/projects/{ref}/analytics/endpoints/logs.all{,.otel}` outside
the `executeAnalyticsSql` wrapper.
- Un-exports `rawSql` and `LogSqlFragmentSeparator` from
`safe-analytics-sql.ts`; updates the `SafeLogSqlFragment` docstring
accordingly.
- Adds an "Analytics SQL" section to
`.claude/skills/safe-sql-execution/SKILL.md` covering the disjoint
`SafeLogSqlFragment` brand, the helpers, the wire boundary, and the new
lint.
## Additional context
Resolves FE-2949
## What kind of change does this PR introduce?
Component update.
## What is the current behaviour?
TanStack tables in the repo are split between the shared `TableHeadSort`
primitive and the older Studio-local `DataTableColumnHeader` helper,
which makes the sorting UI and integration path inconsistent.
If you were to just use `DataTableColumnHeader` in `ui-patterns/Table`,
you’d get a very different visual result to the `TableHeadSort` UI you
see in most other tables.
## What is the new behaviour?
Adds a shared `TanStackTableHeadSort` adapter in `ui-patterns/Table`,
backed by the existing `TableHeadSort` primitive, and switches the
webhook table plus the design-system TanStack demo to that canonical
path. `DataTableColumnHeader` stays as a deprecated wrapper for now,
Studio gets a lint guard to block new imports of it, and the table docs
now point TanStack tables at the shared adapter explicitly.
## To test
Check out column sorting on the Platform Webhook endpoint deliveries
table.
## 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?
The changes required for this React Doctor need more thought that I can
hammer with AI. Just need to stop this from happening in the future with
Ratchet rules
* Use the "eslint" command instead of built-in next lint since it's getting obsolete.
* Bump all deps to support eslint 9+.
* Convert the rules in eslint-config-supabase to be flat-config compatible.
* Migrate all apps to use the new eslint config rules.
* Fix all errors found in the new setup.
* Fix the no default exports ignores.
* Scan all files for linting in studio.
* Fix all lint errors.
* Make the reportUnusedDisableDirectives a warning.