## Context
If you create a table with a `jsonb` **array** column, then try to
insert a new row with `[["` as the `jsonb` column value, the dashboard
currently incorrectly flags it as a large value and blocks the user from
editing it
<img width="583" height="133" alt="image"
src="https://github.com/user-attachments/assets/28b89648-12b6-4b49-a862-b4ec0db03e24"
/>
It's happening due to a small issue in `table-row-query` regarding its
truncation logic, a stop gap to prevent fetching large column values
which will cause browser performance issues. Multi dimension arrays are
being handled differently in which there's no `...` appended at the end.
And hence the naive check in `isValueTruncated` caused this issue to
happen.
Opting to adjust the SQL in `table-row-query` to align the truncation
behaviour for multi dim array, such that it will end with `...` if the
value being fetched is too large.
## To test
We mainly need to ensure that the original problem no longer happens +
truncation of large values is still working as expected. The following
SQL here sets up some sample rows to test the truncation logic in the
UI.
```
create table trunc_test (
id int primary key,
label text,
big_text text,
big_json jsonb,
big_text_1d text[],
big_grid text[][],
big_docs jsonb[][],
small_grid text[][],
small_docs jsonb[]
);
insert into trunc_test values
-- row 1: everything large
(1, 'large', repeat('a', 20000),
jsonb_build_object('k', repeat('b', 20000)),
(select array_agg(repeat('c', 300)) from generate_series(1, 120)),
(select array_agg(array[repeat('d', 300), 'x']) from generate_series(1, 120)),
(select array_agg(array['{"k":1}'::jsonb, to_jsonb(repeat('e', 300))]) from generate_series(1, 120)),
array[array['a','b'], array['c','d']],
array['["x"]'::jsonb]),
-- row 2: everything small
(2, 'small', 'hi', '{"k":1}', array['a'], array[array['a','b'], array['c','d']],
array[array['{"k":1}'::jsonb]], array[array['a','b'], array['c','d']], array['["x"]'::jsonb]);
```
## Problem
Deleting a paid project could show a "Failed to submit exit survey"
toast with a permissions error. The survey was sent after the delete
succeeded. Deleting a project removes its permissions, so the survey
endpoint's project-level access check rejected the request with a 403.
<img width="510" height="196" alt="Screenshot 2026-10-16 at 15 27 40"
src="https://github.com/user-attachments/assets/f01cb89f-597e-492d-bfc4-37f7260c25d3"
/>
## Solution
- Send the exit survey before the delete, while the project still has
its permissions.
- Never block the delete on the survey: a failed survey is ignored.
- Pass a no-op `onError` so the survey failure toast no longer shows.
The old `catch` swallowed the exception but not the hook's default
toast.
## Known trade-off
If the survey succeeds and the delete then fails, a survey is recorded
for a project that still exists. A separate backend change to authorize
deletes against the organization would let the survey move back after
the delete.
## How to test
- Delete a project on a paid plan with a survey reason selected: no
error toast, the survey is recorded, and the project is deleted.
- `pnpm --filter studio exec vitest run
components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.test.tsx`
## Problem
Browser Back and router navigation can leave the pipeline wizard without
confirming unsaved changes. This is a slice of #49243, builds on merged
#51821.
## Solution
- Protect unsaved changes in both runtimes: Close and header links use
the discard dialog; document exits and refresh use the browser warning.
- Add TanStack confirmation for client-side navigation, preserving
Back/Forward history. Step changes do not prompt.
Next.js client-side router exits remain outside this slice. **Start
pipeline** stays disabled.
## Review instructions
1. Open the [Pipelines
preview](https://studio-staging-git-dnywh-featpipeline-navigation-guard-supabase.vercel.app/dashboard/project/_/database/pipelines),
choose a test project and enable **Pipeline creation wizard**. Open the
[wizard
preview](https://studio-staging-git-dnywh-featpipeline-navigation-guard-supabase.vercel.app/dashboard/project/_/database/pipelines/new).
2. Continue to Connection and enter a pipeline name. Try a header link
and **Close**: **Keep editing** preserves values, while **Discard
changes** leaves without a second prompt.
3. Use browser Back to return to Destination without a prompt, then Back
again to leave. Cancel the prompt and check the URL and values remain;
Forward should still reach Connection. Document exits show the native
browser warning in both runtimes; TanStack client-side exits show **Keep
editing / Discard changes**.
4. With changes, refresh and cancel the browser warning. Check values
remain. A fresh wizard with no changes should close without
confirmation.
## Problem
The pipeline wizard lets users reach Review without a valid publication
or initial-sync selection. This is a slice of #49243, following the
merged #51818.
## Solution
- Refresh publication metadata on **Continue** and reject unavailable
publications or removed initial-sync tables.
- Validate initial-sync and advanced data settings, reveal invalid
fields and preserve values for retry.
- Block browser-history shortcuts to Review after data settings change.
| Normal State | Error State (Expanded) |
| --- | --- |
| <img width="1486" height="1010" alt="CleanShot 2026-10-16 at 13 18
25@2x"
src="https://github.com/user-attachments/assets/74ec0785-3bd4-407a-aa3c-7fe69c338488"
/> | <img width="1506" height="2516" alt="CleanShot 2026-10-16 at 13 21
27@2x"
src="https://github.com/user-attachments/assets/6193d8c4-ab9d-47ca-9d9d-1a31198145d1"
/> |
Pipeline creation and navigation-out guards remain for later slices.
**Start pipeline** stays disabled.
## Review instructions
1. Open the [Pipelines
preview](https://studio-staging-git-dnywh-featpipeline-data-validation-supabase.vercel.app/dashboard/project/_/database/pipelines),
select a test project and enable **Pipeline creation wizard**. Open the
[wizard
preview](https://studio-staging-git-dnywh-featpipeline-data-validation-supabase.vercel.app/dashboard/project/_/database/pipelines/new),
choose a destination, then enter credentials and test the connection to
reach Data.
2. Click **Continue** without a publication. Check the field error.
Select a publication, choose a selective initial-sync mode and leave its
table selection empty. Check Continue requires at least one table.
Select a table and confirm you can reach Review.
3. Return to Data. Enter a negative **Batch wait time** under **Advanced
settings**, collapse it and click Continue. Check it reopens with the
error. Correct it and continue, then use browser Back, change a data
setting and try browser Forward. Check you stay on Data until you click
Continue again.
4. For a stale-selection check, select an initial-sync table, remove it
from the publication in another tab, then click Continue. Check the
selection is rejected and your other values remain. A failed metadata
request should also keep values and allow retry with Continue.
## Problem
The pipeline wizard from #51776 allows users to continue without valid
destination credentials. This is the next slice of #49243.
## Solution
- Validate connection fields for all four destinations using the
existing schemas and helpers.
- Validate the destination on **Continue** and advance to Data on
success. Show field errors and connection failures inline; pause for
warnings before continuing.
- Require a new check after connection settings change and block
browser-history shortcuts past an unverified connection.
| Errored Test | Success Test |
| --- | --- |
| <img width="1532" height="2062" alt="CleanShot 2026-10-16 at 12 19
51@2x"
src="https://github.com/user-attachments/assets/a40a6c01-ee89-4601-b5d7-6ffc85071dc7"
/> | <img width="1500" height="1950" alt="CleanShot 2026-10-16 at 12 18
33@2x"
src="https://github.com/user-attachments/assets/c50c1ad4-256b-44b5-bd06-ec0685aae759"
/> |
The screenshots show earlier test states; successful checks now advance
directly to Data.
Data validation and pipeline creation remain for later slices. **Start
pipeline** stays disabled.
## Review instructions
1. Open the [Pipelines
preview](https://studio-staging-git-dnywh-featpipeline-connectio-eb9566-supabase.vercel.app/dashboard/project/_/database/pipelines),
select a test project and enable **Pipeline creation wizard**. Open the
[wizard
preview](https://studio-staging-git-dnywh-featpipeline-connectio-eb9566-supabase.vercel.app/dashboard/project/_/database/pipelines/new)
for that project.
2. Choose a destination and click **Continue** with empty or invalid
fields. Check the errors, then enter valid credentials and retry. A
failed check keeps your values. A failed result stays visible while
editing, until retry or destination change. Changed settings still
require a fresh check. A successful check opens Data automatically;
warnings remain for review before continuing.
3. Continue to Data, return to Connection and change a credential or
advanced connection setting. Check that browser **Forward** cannot skip
the new check, then click **Continue** to validate again.
## Summary
The incident.io status page is now the only status banner for everyone,
so the legacy status banner path is removed:
- Removed the `incidentIoStatusPage` ConfigCat flag. The incident.io
path is unconditional.
- Deleted the legacy status banner code (`StatusPageBanner` and its
utils/visibility hook) and its tests.
- Deleted the legacy API endpoints `/api/incident-banner`,
`/api/incident-status`, and `/api/status-override` (pages, app, and
routes variants), along with their data hooks, query keys, and lib
helpers.
- Removed `STATUSPAGE_*` env vars from the Turbo config and the
`INCIDENT_BANNER_DISMISSED_IDS` local storage key from
`packages/common`.
- Updated the AI tools and SQL generation endpoint that read the flag.
`NEXT_PUBLIC_ONGOING_INCIDENT` / the `ongoingIncident` override is kept.
The `incident_status_cache` table and its migration are intentionally
untouched. Studio no longer reads it, and dropping it is a separate
cleanup.
## Testing
- Studio typecheck passes.
- Related vitest suites pass (414 tests).
## Summary
Migrates four more assistant tools to the contract + `.server.ts`
pattern used by the notebook tools. Each tool now declares its schemas,
opt-in level, approval and model-visible output once.
## Changes
* Add contracts and server factories for `execute_sql`,
`deploy_edge_function`, `rename_chat`, `load_knowledge`;
`getStudioTools` now composes them.
* Add `execute-sql-output.ts` (output schema, `toModelRows`,
`NO_DATA_PERMISSIONS`) and `knowledge-topics.ts` (topic names + UI
labels, replacing the copy in `Message.Parts.utils.ts`).
* Delete `executeSqlSanitizer` and `ALL_TOOL_SANITIZERS`; history replay
goes through `sanitizeContractPart` only.
* Drop the four tools from `TOOL_CATEGORY_MAP`; the UI and evals import
schemas from the contracts; `rename_chat` input casts now use the
contract type.
## Deviations worth reviewing
* `execute_sql` replay: a bare-array legacy output is stamped `schema`
via a union transform (still hidden from the model, still shown in the
UI). A malformed non-array output is now dropped, where before it passed
through at the data level.
* The "no data permission" message now reaches the model as a JSON
string value instead of a text part. The text the model sees is
unchanged.
* `deploy_edge_function` output is now strictly `{ success: boolean }`
in the UI parse (was optional/passthrough).
## Problem
Changing the restore date resets the entered time, and the repeated hour
at the end of daylight saving time can resolve to the wrong occurrence.
Both can change the submitted recovery timestamp.
## Fix
Store the recovery point as a Unix timestamp and preserve its occurrence
when editing an ambiguous local time. Calendar edits carry the entered
hours, minutes, and seconds onto the selected day, applying that day's
timezone offset and retaining the existing backup-range clamping.
This focuses on date edits and repeated-hour handling from #48903. It
changes only the PITR form, its utilities, and regression tests. Related
to
[FE-4129](https://linear.app/supabase/issue/FE-4129/r2np-datetime-widget).
## How to test
- In PITR or restore-to-new-project, enter a time and select another
available calendar day. Expected result: the entered hours, minutes, and
seconds remain selected, and the submitted timestamp matches the new
date.
- Run the PITRForm and PITR.utils Vitest suites. Their fixtures cover
both occurrences of the repeated Madrid/New York hour, edits across DST,
spring-forward normalization, fractional offsets, and backup boundaries.
- Twelve focused regression cases passed under a Madrid browser
timezone. Those cases plus the existing selector component test passed
under Hong Kong, thirteen tests total. All CI checks passed, including
Studio tests, all four Next.js/TanStack E2E shards, typecheck, lint
ratchet, formatting, spelling, unused-code checks, and Docker build.
## Problem
New projects created on staging default to v2 (`cloud_provider: 'AWS'`).
We want staging to start exercising v3 (`AWS_K8S`). Resolves FE-4640.
## Solution
In `ProjectCreationForm`, the initial cloud provider is now rolled once
per mount: on staging (`NEXT_PUBLIC_ENVIRONMENT === 'staging'`) it is
`AWS_K8S` with 50% probability, otherwise the existing default. v3 is
only rolled if the `infra:cloud_providers` custom content allows
`AWS_K8S`. Prod and local are unchanged, and the internal-only "Cloud
provider" selector still lets admins override the default.
## Review instructions
1. Run Studio with `NEXT_PUBLIC_ENVIRONMENT=staging` and the
`newProjectInternalOnlyConfiguration` flag on.
2. Reload the new-project page several times and check that the "Cloud
provider" select defaults to AWS and AWS (Revamped) about equally often.
3. Change the select manually and confirm the choice is kept and the
project is created with it.
4. With `NEXT_PUBLIC_ENVIRONMENT=prod` or `local`, confirm the default
is always AWS.
## Checklist
- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
- [ ] If I wrote a new docs topic or edited an existing topic, I used
the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs
[style
guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
## Problem
The time-range dropdown above the request charts on the project home
page resets on every visit, so users who prefer a different range have
to re-select it each time.
Resolves FE-2828.
<img width="819" height="353" alt="Screenshot 2026-10-16 at 10 42 05"
src="https://github.com/user-attachments/assets/cb8d467b-e709-43b0-9ae4-b02ebccf5faf"
/>
## Solution
Persist the selection in localStorage, keyed per project
(`home-chart-interval-${ref}`), in both usage section variants
(`ProjectUsageSection` and `ProjectUsageSectionDeltas`, chosen by the
`newHomepageUsageDeltas` flag).
- New `resolveChartInterval` helper validates the stored value against
the known keys and the plan's log retention, falling back to the plan
default for corrupt values or after a plan downgrade. Unit tested.
- Removed the `useEffect` in `ProjectUsageSection` that reset the
interval on retention change, since it would overwrite the saved value.
- Moved `getDaysRequired` into `logs.utils.ts` so the dropdown and the
helper share the retention check.
Manually verified with the flag on and off: reload keeps the selection,
and each project keeps its own.
## Problem
Several snippets in the dashboard Connect sheet still mention anon keys
in placeholder text, even though publishable keys are now the
recommended client key:
- Swift, Kotlin, Expo, Ionic React and Ionic Angular show `<prefer
publishable key instead of anon key ...>` when the project has no
publishable key.
- Astro, Nuxt, Refine, TanStack, Flask and the three .NET snippets fall
back to `your-anon-key` when the project has neither key.
- Ionic React stores the key in a variable called `supabaseAnonKey`.
## Solution
- Both placeholders become `your-publishable-key`.
- The Ionic React variable becomes `supabaseKey`, matching the other
snippets.
Snippets that switch to `*_ANON_KEY` environment variables for projects
with only legacy keys (Next.js, Vite, SvelteKit and others) are
unchanged. There, `your-anon-key` still matches the variable name.
The Flutter snippet is handled separately in #51792.
## Review instructions
1. Open the Connect sheet for a project without a publishable key.
2. Go through the frameworks listed above and check that the
placeholders say `your-publishable-key`.
## Checklist
- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
## Problem
The feature flag enabling the new scoped oauth apps UI was enabled by
mistake in production but this UI currently only work with mocks. We
added an environment check in some places but not all so users did see
the new UI
## Solution
Add the environment check wherever needed
## Context
Replaces the individual filters on the Account audit logs page with the
filter bar to match a consistent UX across log surfaces
<img width="1206" height="360" alt="image"
src="https://github.com/user-attachments/assets/c5bdfd7e-3277-4695-b9fc-1791df8720c7"
/>
Also uses the `supportOperators` param for the `FilterBar` to support
"OR" based operations when filtering for projects
<img width="1219" height="97" alt="image"
src="https://github.com/user-attachments/assets/d4748e6c-d1f3-4f23-b767-e0f4bb1c0a1c"
/>
The main changes for the `FilterBar` component made in this PR (which
would be more relevant for the design team) are:
- Added a divider between the options and "New group"
- Originally didn't have a divider
- So there's a clearer distinction that the "New group" isn't a option
to set as the value for
- Add a new `defaultGroupOperator` prop
- Originally defaults to `AND`
- However, in the context of Audit Logs when filtering for projects,
you'd expect `OR` to be the default to filter across several projects
(`AND` wouldn't make sense here)
## Problem
The file list in an Edge Function's code view is shown in the order the
Management API returns it (roughly `created_at` order), not sorted by
name. Users expect name order, as in VS Code.
Resolves FE-2799
## Solution
- `formatFunctionBodyToFiles` now sorts files by their displayed name
before assigning ids, using a case-insensitive numeric comparison
(`file2` before `file10`). The list is flat, so there is no folder
grouping.
- Sorting would have changed which file opens first, so the code page
now selects the function's entrypoint file by default.
`getDefaultSelectedFileId` matches the entrypoint's basename, then falls
back to `index.ts`, then to the first file. The entrypoint isn't always
`index.ts`: the docs allow any `.ts`, `.js`, `.tsx`, `.jsx` or `.mjs`
file.
- The shared `FileExplorerAndEditor` and the new-function page are
unchanged. The code page passes the selection through the explorer's
existing `selectedFileId` / `setSelectedFileId` props.
- Files added or renamed in the editor keep their position until the
next load.
Before:
<img width="259" height="370" alt="Screenshot 2026-10-15 at 16 44 50"
src="https://github.com/user-attachments/assets/611fe2b9-5676-496a-85a9-e50032708c7a"
/>
After:
<img width="259" height="371" alt="Screenshot 2026-10-15 at 16 35 19"
src="https://github.com/user-attachments/assets/7f433661-ef34-4e3f-9143-afc67be1b30a"
/>
## Review instructions
1. Open an Edge Function's Code tab for a function with several files,
for example `utils.ts`, `index.ts`, `deno.json`.
2. Confirm the files are listed alphabetically and that `index.ts` is
selected on load.
3. For a function with a custom entrypoint (for example `main.ts`),
confirm that file is selected on load.
4. Run `pnpm test:studio -- components/interfaces/EdgeFunctions`.
## Problem
The pipeline wizard in #49243 needs its merged components composed into
a full-page flow, with values preserved when moving backwards and
forwards.
## Solution
- Compose four full-page SteppedFlow steps behind **Pipeline creation
wizard**.
- Preserve values through step navigation and browser Back/Forward.
Refresh starts at Destination.
- Reuse Studio’s header controls and confirm before discarding changes.
- Defer step validation and creation; **Start pipeline** is disabled.
**Add pipeline** and preview-off visits keep the sheet.
| After |
| --- |
| <img width="1024" height="759" alt="New Pipeline Supabase"
src="https://github.com/user-attachments/assets/7c3c4511-9a00-4e64-a86a-d6d9489f7d75"
/> |
| _Only available by navigating to `database/pipelines/new` with the
feature preview on._ |
## Review instructions
1. Open the [Pipelines
preview](https://studio-staging-git-dnywh-featpipeline-stepped-n-ce5307-supabase.vercel.app/dashboard/project/_/database/pipelines),
select a test project and enable **Pipeline creation wizard**. Open the
[wizard
preview](https://studio-staging-git-dnywh-featpipeline-stepped-n-ce5307-supabase.vercel.app/dashboard/project/_/database/pipelines/new)
for that project. Check the full-page layout and header at desktop and
phone widths.
2. Enter connection details, select a publication and change
initial-sync and advanced settings. Use **Back/Continue**, browser
**Back/Forward**, and switch destinations to confirm values remain.
3. Check Review shows your settings, masks secrets and disables **Start
pipeline**. Refresh and confirm it restarts at Destination. Incomplete
steps are allowed in this navigation-only slice.
4. With changes, try header navigation or **Close**. Check **Keep
editing** preserves values and **Discard changes** leaves the flow. Turn
the preview off and confirm the wizard URL opens the sheet.
## Problem
Lucide React v1 improves screen reader behaviour (icons are hidden from
assistive tech by default). It also removes brand icons such as GitHub.
We were still on Lucide 0.x across the monorepo.
## Solution
- Move every workspace onto one shared Lucide React v1 version via the
pnpm catalog.
- But Lucide React v1 [deprecated brand
icons](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md)
- Meaning we need to handle any _old_ brand icons we used from Lucide
0.x.
- Luckily, that was just `GitHubIcon`. So...
- Add a `GithubIcon` in `packages/ui` that matches Lucide’s previous
GitHub icon, and use it wherever we imported GitHub from Lucide.
- No other Lucide brand icons were in use.
- Studio “Continue with GitHub” sign-in still uses the existing SVG
asset (`github-icon.svg`), not this component.
- Keep the `Icon` suffix import style [recently
introduced](https://github.com/supabase/supabase/pull/51659)
- Monorepo apps now use `catalog` Lucide React v1.
## Review instructions
1. Open [Studio
preview](https://studio-staging-git-dnywh-bump-lucide-react-v1-supabase.vercel.app/dashboard/org)
on a project that is connected to GitHub:
- Project home → Activity stats “GitHub” row
- Org project list → GitHub badge on a connected project card/row
- Branches → GitHub link on a branch row, or “Sync with a GitHub branch”
- Project Settings → Integrations → GitHub integration panel
2. Open
[/kb/guides/sample-guide](https://supabase.com/kb/guides/sample-guide) →
confirm “Go to the GitHub discussion” shows the icon.
3. Open
[/docs/reference/javascript](https://supabase.com/docs/reference/javascript)
→ confirm the header GitHub control still shows the icon. Or
[/docs/guides/troubleshooting/rls-simplified-BJTcS8](https://supabase.com/docs/guides/troubleshooting/rls-simplified-BJTcS8)
→ “View discussion on GitHub”.
## Problem
Updating our region experiment flags to be able to handle latest
experiment.
## Solution
Make flags resolve independently. cc @charislam
## Review instructions
tbc
---------
Co-authored-by: Charis Lam <26616127+charislam@users.noreply.github.com>
## What
Aligns the webhooks FE with backend changes confirmed by Pawel
(FE-4621):
1. `custom_headers` is optional but never null, matches the updated
contract.
2. Removed the client-only `name` field (never persisted by the API).
Display name is now the endpoint's URL plus its subscribed event types.
3. The deliveries list endpoint now returns the full `event` object, so
`eventType`/`eventPayload` are always populated. Added an Event column
to the deliveries table.
Also regenerates `packages/api-types` from production to pick up these
changes.
While testing, found and fixed two more related issues:
4. A delivery can briefly exist before the backend finishes attaching
its event data, regardless of what triggered it (test, retry, or real
traffic). The deliveries list now polls every 5s while open, so new or
still-settling deliveries fill in automatically instead of requiring a
manual refresh.
5. "Send test event" always defaulted to an unrelated sample event type.
The test endpoint actually accepts a specific `event_type`, so this now
sends the endpoint's own subscribed type automatically, or shows a
picker when there's more than one to choose from.
## Testing
All webhooks component/unit tests pass (30/30), typecheck and lint
clean. Manually verified on localhost against a live backend.
## Note
This may clash with #51614 (`custom_headers: null` in a test fixture, no
longer a valid value once this merges). Will follow up there after this
lands.
## Summary
Migrates the create, delete, and run notebook assistant tools to the
tool contract + server pattern used by `update_notebook`. Each tool now
defines its schema once in a shared contract, with server-only execution
split into a `.server.ts` module. This removes duplicated client-side
schemas and gives the three tools one shared notebook mutation output
schema.
## Changes
- Add `create-notebook`, `delete-notebook`, and `run-notebook` tool
contracts, each with a `.server.ts` implementation.
- Remove the duplicated client-side schemas for these tools from
`notebook-tools.ts` and the registry.
- Add `notebook-mutation-output.ts` as the single output schema shared
by the notebook mutation tools.
- Add `lib/ai/opt-in-level.ts` with the opt-in level helpers, extracted
from the existing module to break an import cycle.
- Add optional `ToolContract.outputOnInvalid` so `run_notebook` history
replay can reuse a stored output when input fails validation.
- Update the AI assistant panel renderers (proposal, run, opt-in modal
and request) and the tool filter, sanitizer, and cache invalidation to
use the new contracts.
## Deviations worth reviewing
- **`outputOnInvalid` on `ToolContract`.** This is a new optional hook.
It exists so `run_notebook` history replay can return a valid output
when stored input no longer passes validation. Reviewers should confirm
this is the right level for the hook rather than handling it inside the
tool.
- **`opt-in-level.ts` extraction.** The helpers were moved into their
own module to break a circular import between the tool modules and the
opt-in components. This adds one file, but no behavior change is
intended.
- **`create-notebook` contract imports the data-layer
`notebook-schema`.** This matches `update-notebook`. It should be moved
behind the contract before the planned ESLint rule step, which restricts
contract modules from importing data-layer code.
## Testing
- `vitest` for `lib/ai`, `components/ui/AIAssistantPanel`, and `state`:
920 tests pass across 71 files.
- `tsc`: one error remains in `packages/ui-patterns`
(`InstructionBlocks.tsx`), which is unrelated to this change. I have not
verified whether it also fails on the base branch.
- ESLint reports no errors on the changed files (9 `no-explicit-any`
warnings in `tool-filter.ts` and its test); Prettier is clean.
- Not run: knip and the lint ratchet.
## Problem
The Flutter snippet in the dashboard Connect sheet calls
`Supabase.initialize` with `anonKey`. The current stable
`supabase_flutter` (2.18.3) deprecates `anonKey` in favor of
`publishableKey`, so the copied code shows a deprecation warning right
away.
## Solution
Pass the key as `publishableKey` and change the fallback placeholder to
`your-publishable-key`. The old placeholder told users to prefer the
publishable key over the anon key, which no longer makes sense once the
parameter itself is named `publishableKey`.
## Review instructions
1. Open a project in the dashboard and click Connect.
2. Choose Framework, then Flutter.
3. Check that `lib/main.dart` calls `Supabase.initialize(url: ...,
publishableKey: ...)`.
## Checklist
- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
## What
Moves the "Minimum interval per user" (SMTP_MAX_FREQUENCY) setting from
the Custom SMTP page to the Rate Limits page. The field is always
visible there now, disabled with a tooltip when Custom SMTP isn't
configured, linking to the SMTP settings page.
## Why
This setting can cause `over_email_send_rate_limit` errors even when all
settings on the Rate Limits page allow more traffic, because it lived
only on the SMTP page. Consolidating it onto Rate Limits makes the
actual rate-limiting behavior visible in one place.
Fixes FE-4627
## Testing
- Verified field is disabled with explanatory tooltip when Custom SMTP
is off
- Verified field becomes editable and saves correctly once Custom SMTP
is configured
- Confirmed field no longer appears on the SMTP settings page
- Ran typecheck, lint, and SmtpForm unit tests locally
## Summary
Fixes FE-4635: `UpdateRolesPanel` only loaded the first page (96) of org
projects. Instead of fetching every page, the team settings no longer
need the full project list.
```diff
<UpdateRolesPanel>
useOrgProjectsInfiniteQuery({ slug }) // only pagination.count is used
- orgProjects = all loaded projects
- noAccessProjects = orgProjects - projectsRoleConfiguration
- toggle off "Apply to all projects" -> select every org project
+ toggle off "Apply to all projects" -> start with no projects selected
- <Collapsible> listing every project without access
+ <Admonition> "access to N projects out of M total projects"
```
```diff
MemberRow
- orgProjects from TeamSettingsDataContext
- projectNameByRef = Map(orgProjects)
- projectsApplied = role.projects.map(lookup name).filter(has name)
+ projectsApplied = role.projects // roles API already returns { name, ref }
<HoverCardContent>
+ appliesToAllProjects
+ ? "<role> role applies to M projects" // no project list
+ : "<role> role applies to N projects" + list of projects
```
```diff
MembersView / TeamSettingsDataContext
- useOrgProjectsInfiniteQuery
- orgProjects
```
## Evidence
- **Before:** orgs with more than 96 projects: toggling off "Apply roles
to all projects" assigned only the first 96, and the "no access" list
was capped at 96. API logs showed only `offset=0` requests from the team
page.
**After:** no code path depends on the loaded project list, so the 96
cap no longer applies. The screenshots below show the new UI. No
automated test covers orgs with more than one page of projects.
## Merge Danger
**Door:** two-way
**Blast Radius:** Team settings
- Toggling off "Apply roles to all projects" now starts with nothing
selected instead of every project.
- The member row hover card for org-wide roles shows only the project
count, not the project list.
- The "no access" project list in the panel is replaced by a count.
New UI:
<img width="602" height="381" alt="Screenshot 2026-10-15 at 14 11 17"
src="https://github.com/user-attachments/assets/39d5ef62-5beb-4e14-9c6f-740bd7f113a1"
/>
<img width="593" height="334" alt="Screenshot 2026-10-15 at 14 11 23"
src="https://github.com/user-attachments/assets/ec06bbc4-b2af-458f-b664-894e9567ff23"
/>
<img width="637" height="248" alt="Screenshot 2026-10-15 at 14 11 38"
src="https://github.com/user-attachments/assets/e4ef9774-58eb-415d-aab8-f0497e3fef2a"
/>
<img width="496" height="431" alt="Screenshot 2026-10-15 at 14 11 46"
src="https://github.com/user-attachments/assets/d1618ce3-a03d-41ec-985d-ca5936c754a7"
/>
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
## Problem
The "Outstanding invoices in other organization" banner asks users to
pay invoices to avoid service disruption, but never says which
organization has them. Users with several organizations have to guess
where to go.
The banner also looked at only the first overdue organization. Its link
pointed at that organization's billing page without naming it, and if
the lookup failed, it fell back to the current organization's billing
page.
Linear: FE-4587
## Solution
- `useOrganizationRestrictions` now collects every other organization
with overdue invoices from the organizations list. It dedupes them,
excludes the current one, and orders them by name, which the list
already is. It no longer falls back to the current organization's slug.
- The banner names only the first organization, linked to its billing
page: "Pay invoices for **Acme** to avoid service disruption". When more
organizations are affected it adds a count: "…for **Acme** and 2
others…". Paying the first often clears the rest, and the next one
appears once it's resolved.
- If no affected organization can be resolved, for example the user lost
access, the banner shows "Pay outstanding invoices to avoid service
disruption", linking to `/organizations`.
- The title is now plural: "Outstanding invoices in other
organizations".
- The dev toolbar's "overdue invoices from other org" override now
injects overdue invoices for every real other organization. It
previously used a made-up ID, so the banner could never show a name. I
also replaced a nested ternary there, per Studio's style rules.
For one org:
<img width="1022" height="42" alt="Screenshot 2026-10-14 at 12 01 32"
src="https://github.com/user-attachments/assets/8f34c2ff-d47f-45fc-80a4-c7cdc3df558b"
/>
For multiple orgs:
<img width="1057" height="42" alt="Screenshot 2026-10-14 at 13 39 42"
src="https://github.com/user-attachments/assets/2100b0b5-65a3-4d7c-8521-48d8e71b62ef"
/>
Note: Overdue invoice counts are not shown by design, only that one or
more overdue invoices exist for an org.
## Review instructions
1. Run Studio in platform mode with at least three organizations, and
open a project page.
2. Enable the dev toolbar from the account menu ("Local tools" > "Dev
toolbar").
3. In the Warnings tab when on a Project page, choose "Overdue invoices
from other org".
4. Check that the banner names the first other organization
alphabetically, shows "and N others" when there's more than one, and
that the name links to that organization's billing page.
5. Use "Reset to real data" to clear the override.
Tests: `pnpm --filter studio exec vitest run
components/interfaces/Organization/restriction.constants.test.tsx
hooks/misc/useOrganizationRestrictions.test.tsx`
### Mocking in QA
Mock `/platform/stripe/invoices/overdue` for overdue invoice counts in
different organizations.
Note: The endpoint will not return an org in the result if the overdue
invoice count is zero.
## Checklist
Check all before review:
- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
- [x] If I wrote a new docs topic or edited an existing topic, I used
the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs
[style
guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide)
(not applicable)
## Problem
It can be hard to verify users roles in organizations with many members
## Solution
Add a filter on the team pages for organization roles.
<img width="1174" height="310" alt="image"
src="https://github.com/user-attachments/assets/dfc2ccdc-b83e-45c5-9a6c-d50417a3ff3e"
/>
## Review instructions
- Open the organization team page
- Select a role and verify only members who have this role are displayed
- Ensure other filters still work
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
## Problem
attempt to [fix this
ticket](https://supabase.frontapp.com/inboxes/teams/folders/27650590/unassigned/116845868830):
when the studio ai assistant runs a query with previously set database
opt-in, `execute_sql` returns every row while the query cell's footer
says "Limit 100 rows" causing in case of large amount of returned rows
browser to freeze
+ the chat fails with "Sorry, I'm having trouble responding right now"
until the user clears it
## Solution
- `execute_sql` now keeps the first 100 rows using the cell's existing
`DEFAULT_CELL_ROW_LIMIT`
- uses in history sanitizer the same `toModelRows` helper
- chats saved before this change have no `totalRows` so the helper falls
back to `rows.length` and cuts them to 100 too
<!--
## Preview links
If relevant, include links to changed pages for easy review access.
Copy the preview base URL from the Vercel bot comment on this PR. Use
the following table as an example template.
| Site | Live | Preview | Search for |
| -------------- |
-------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------
| ----------------------------- |
| WWW | [/blog/your-post](https://supabase.com/blog/your-post) |
[/blog/your-post](https://zone-www-dot-com-git-branch-name-supabase.vercel.app/blog/your-post)
| unique phrase from the change |
| Docs |
[/docs/guides/your-page](https://supabase.com/docs/guides/your-page) |
[/docs/guides/your-page](https://docs-git-branch-name-supabase.vercel.app/docs/guides/your-page)
| unique phrase from the change |
| Studio | [/dashboard](https://supabase.com/dashboard) |
[/dashboard](https://studio-git-branch-name-supabase.vercel.app/dashboard)
| unique phrase from the change |
| Design system | [/design-system](https://supabase.com/design-system) |
[/design-system](https://design-system-git-branch-name-supabase.vercel.app/design-system)
| unique phrase from the change |
| UI library | [/library](https://supabase.com/library) |
[/library](https://ui-library-git-branch-name-supabase.vercel.app/library)
| unique phrase from the change |
| Knowledge base |
[/kb/guides/your-page](https://supabase.com/kb/guides/your-page) |
[/kb/guides/your-page](https://kb-git-branch-name-supabase.vercel.app/kb/guides/your-page)
| unique phrase from the change |
-->
<!-- ## Additional context
Optionally add any other context or screenshots.
-->
## Review instructions
1. In a test project, run this in the SQL editor:
```sql
create table "emailSubscriber" (id bigint generated always as identity
primary key, email text);
create table "stripeCustomer" (id bigint generated always as identity
primary key, email text);
insert into "emailSubscriber" (email) select md5(g::text) ||
'@example.com' from generate_series(1, 50000) g;
insert into "stripeCustomer" (email) select md5(g::text) ||
'@example.com' from generate_series(1, 1000) g;
2. on the live dashboard, open the assistant in a new chat and send "i
want all the emails in emailSubscriber where the email is not in the
stripeCustomer table"
3. in the opt in requested settings choose "Schema, Logs & Database
Data"
4. then click run query, the cell should show 100 rows (vs 50000 rows in
prod)
## Checklist
Check all before review:
- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
- [x] If I wrote a new docs topic or edited an existing topic, I used
the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs
[style
guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide)
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
## Problem
After a support ticket is resolved, customers receive an email with a
link to a satisfaction survey. That survey page is currently served from
a standalone function outside this repo. We are consolidating it into
the dashboard so it is deployed, monitored and tested with everything
else.
Linear:
[TOOLING-1425](https://linear.app/supabase/issue/TOOLING-1425/build-csat-in-supase-studio)
## Solution
Adds a public page at `/support/csat` that collects the survey answer
and posts it to a new Platform endpoint, `POST /platform/support/csat`,
which records the answer on the support conversation.
Key choices:
- **Pre-auth page**, modelled on `/verify-email`. Customers arrive from
an email and may not have a dashboard session. Hosted platform only;
self-hosted routes to 404.
- **URL shape** `?conversation_id=cnv_…&helpful=yes|no` matches the
links already sent in survey emails, so the email template only needs
its host changed. `helpful` preselects the answer, defaulting to Yes.
- **Copy and behaviour match the existing survey page**: same title,
question, labels, button text, and result messages.
- **Form** uses react-hook-form + zod; Yes/No are toggle buttons, the
comment is optional.
- **States**: missing or malformed ticket reference, already submitted
(409), ticket mismatch (404), and a generic error that keeps the form so
the customer can retry.
- The mutation uses `fetchHandler` directly, like the email-verification
mutation, because the endpoint is not in the generated API types yet.
Screenshot:
<img width="977" height="801" alt="image"
src="https://github.com/user-attachments/assets/2331861e-e8e9-43e1-84fc-3bf5786c145e"
/>
Depends on the Platform endpoint
https://github.com/supabase/platform/pull/40027. Until it is deployed,
submissions fail with the generic error state.
## Review instructions
1. Run Studio against a Platform stack that has the `POST
/platform/support/csat` endpoint.
2. Open `/support/csat?conversation_id=<a support conversation
id>&helpful=yes` while logged out. The page renders without redirecting
to sign in, with Yes preselected.
3. Pick an answer, optionally add a comment, and submit. The success
message appears and the answer is recorded on the conversation.
4. Submit again for the same conversation: the page reports that the
survey has already been submitted.
5. Open the page with no `conversation_id`: the page reports an invalid
ticket.
6. `pnpm --filter studio exec vitest --run
components/interfaces/Support/CsatSurvey` passes (18 tests).
## Checklist
Check all before review:
- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
- [x] If I wrote a new docs topic or edited an existing topic, I used
the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs
[style
guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide)
---------
Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com>
## Context
Initial feedback was mainly that the "Previously used keys" table was
scrollable while the top table wasn't which was a bit inconsistent UX,
so this PR addresses that but also makes a couple of UI adjustments to
the JWT keys page to tidy things up
### Before
<img width="1142" height="912" alt="image"
src="https://github.com/user-attachments/assets/ca5f8bc6-8b73-4d96-9055-24a39544fc06"
/>
### After
- Removed color indicators + badges on the key's status and ID - reckon
these are unnecessary signals and plain texts work fine. Opted for
`text-foreground-lighter` for keys which status isn't `in_use`, mainly
to highlight the current key which is the most important imo
- Removed icons for status and type, also reckon unnecessary as they
don't really communicate the context much quicker than their texts
- Removed "Legacy" prefix on the type column (e.g for HS256) - reckon
unnecessary, we don't label this as legacy either in the create standby
key dialog, doesn't really communicate any implications
- Standardized widths of status, key id, type, and last used columns -
these values are fixed and won't deviate, so fixing the widths will
allow a more uniform layout across the 3 tables
- Prioritise order of "current" key above "standby" key
- Use ButtonTooltip + default variant for the "More actions" CTA, also
removed the "Actions" header text
- "Last rotated at" column only shows up on `xl` viewport breakpoint
rather than `lg`
- Remove a number of unnecessary class names that were applied on the
`Table` components
<img width="1145" height="909" alt="image"
src="https://github.com/user-attachments/assets/af596931-a126-4883-999b-829a9c9359fc"
/>
## Context
The audit logs endpoint isn't paginated, so we could be fetching a lot
of rows at once
e.g on the Supabase Dev org, the orgs audit logs were pulling 4k rows
from the endpoint which i noticed a bit of browser slow down - so this
should alleviate the browser performance impact
There shouldn't be any visual difference from the changes here 🙏
## Problem
The pipeline wizard in #49243 needs estimated costs and validation
feedback in review before creation.
## Solution
Add the megabranch’s inline cost estimate and validation feedback to
preview-enabled creation. Initial-sync costs follow the selected tables;
ongoing pipeline and streaming rates remain visible. Missing estimates
do not block creation. Validation feedback distinguishes critical issues
from warnings.
Preview off and editing keep the existing layout. The Start action,
warning confirmation and final cost dialog stay unchanged; no wizard
routing is added. Stacked on #51635.
## Review instructions
1. Open the [Pipelines
preview](https://studio-staging-git-dnywh-featpipeline-review-costs-supabase.vercel.app/dashboard/project/_/database/pipelines),
select a test project with Pipelines enabled, enable **Pipeline creation
wizard** in Feature Previews and click **Add pipeline**. Select a
publication and confirm **Estimated costs** follows Data review.
2. Compare **All tables**, **No tables** and selected-table initial
sync. Confirm initial-sync totals follow the selection, while hourly
pipeline and streaming rates stay visible. With **No tables**, initial
sync should cost zero. Check desktop and phone widths.
3. Enter valid-format but incorrect destination credentials and click
**Start pipeline**. Confirm critical validation issues appear inline and
creation stops. Correct the credentials and retry. If warnings are
returned, confirm the review explains they may affect replication and
the existing **Start pipeline anyway** confirmation still applies.
Cancel before final creation.
4. Turn the preview off and reopen **Add pipeline**. Confirm there is no
inline cost review. With the preview on, edit an existing pipeline and
confirm its original layout remains.
## Problem
The pipeline wizard in #49243 needs publication, initial sync and custom
settings reviewed before creation.
## Solution
Extend the preview-enabled review from #51633 with Data, custom
connection settings and configured BigQuery table layouts. Values update
as fields change. Unchanged advanced defaults and unconfigured table
layouts are omitted; an explicit maximum staleness of zero is shown.
Preview off and editing keep their existing layout. Validation and
creation behaviour stay the same; no wizard routing is added. Stacked on
#51633.
| This PR: Simple Case | This PR: Extreme Case | Reference: Final Wizard
|
| --- | --- | --- |
| <img width="1244" height="1994" alt="CleanShot 2026-10-14 at 11 22
16@2x"
src="https://github.com/user-attachments/assets/5c2c9ea4-7b2b-4f0b-8ebe-0cfc2ac27059"
/> | <img width="1226" height="3568" alt="CleanShot 2026-10-14 at 12 02
16@2x"
src="https://github.com/user-attachments/assets/dc556d4a-5130-4b6c-80b5-8644603d9a55"
/> | <img width="1486" height="2364" alt="CleanShot 2026-10-14 at 10 50
30@2x"
src="https://github.com/user-attachments/assets/982b7d23-9449-4841-b002-9e30fbbabfe9"
/> |
## Review instructions
1. Open the [Pipelines
preview](https://studio-staging-git-dnywh-featpipeline-data-review-supabase.vercel.app/dashboard/project/_/database/pipelines),
select a test project with Pipelines enabled, enable **Pipeline creation
wizard** in Feature Previews and click **Add pipeline**. Confirm
**Data** review follows the destination review.
2. Select a publication. Confirm its name, table count and initial-sync
choice appear as read-only fields. Choose selected tables to include or
exclude and confirm their schema-qualified names appear. Change
publication and confirm old tables are not shown.
3. Under Data’s **Advanced settings**, change batch wait time, table
sync workers or initial sync connections. Confirm only custom values
appear in review. Restore defaults and confirm those rows disappear. For
BigQuery, configure partitioning or clustering and confirm the table
name and wrapping layout summary appear in the main column under **Table
layout**; unconfigured layouts should be omitted.
4. Change BigQuery connection pool size or maximum staleness and confirm
they appear within **Connection**. If available, check DuckLake pool
size and Snowflake role too. Check desktop and phone widths, then cancel
and discard changes; no credentials or creation are needed.
5. Turn the preview off and reopen **Add pipeline**: confirm no review.
With the preview on, edit an existing pipeline and confirm no review
there either.
## Problem
The pipeline wizard in #49243 needs a review of destination details
before creation.
## Solution
Add `PipelineDestinationReview` after Data in preview-enabled creation.
It uses the megabranch’s Destination and Connection sections,
destination logo, read-only fields and field descriptions. Values update
as fields change. Credentials are omitted. Custom DuckLake catalog URLs
and ClickHouse endpoints are masked as whole fields, with an eye toggle;
changing a URL masks it again.
Preview off and editing keep the existing layout. Validation and
creation behaviour stay the same; no wizard routing is added.
Publication, advanced settings and cost review will follow in later
slices.
| This PR | Reference: Final Wizard |
| --- | --- |
| <img width="1254" height="1308" alt="CleanShot 2026-10-14 at 11 01
05@2x"
src="https://github.com/user-attachments/assets/e792ed7f-d4f4-4557-b338-8a120c8663b5"
/> | <img width="1486" height="2364" alt="CleanShot 2026-10-14 at 10 50
30@2x"
src="https://github.com/user-attachments/assets/982b7d23-9449-4841-b002-9e30fbbabfe9"
/> |
## Review instructions
1. Open the [Pipelines
preview](https://studio-staging-git-dnywh-featpipeline-destinati-25606b-supabase.vercel.app/dashboard/project/_/database/pipelines),
select a test project with Pipelines enabled and go to **Database →
Pipelines**. Enable **Pipeline creation wizard** in Feature Previews,
then click **Add pipeline**. Confirm the review follows **Data**, with
**Destination** and **Connection** sections.
2. With BigQuery selected, enter a pipeline name, project ID and dataset
ID. Confirm the review updates with those values in read-only fields,
includes field descriptions and the destination logo/region, and omits
the service-account key. Check desktop and phone widths.
3. If available, select DuckLake with a custom catalog. Enter a catalog
URL and data path. Confirm the whole URL is hidden in review, use the
eye toggle to reveal/hide it, then change the URL and confirm it is
hidden again. For Supabase DuckLake, confirm the bucket is shown
instead. Snowflake shows account/database/schema without its private
key; ClickHouse shows database/engine and a masked endpoint without its
password.
4. Cancel and discard changes; no pipeline creation is needed. Turn the
preview off and reopen **Add pipeline**: confirm there is no review
section. With the preview on, edit an existing pipeline and confirm
there is no review section there either.
## 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?
Bug fix
## What is the current behavior?
Fixes#51574
Deleting several selected rows from a table with a composite primary key
builds one `IN` list per key column:
```sql
delete from public.user_roles where user_id in (1,2) and role_id in ('admin','viewer');
```
That matches the cartesian product of the selected values, so rows the
user never selected are deleted too. In the example above, selecting
`(1, 'admin')` and `(2, 'viewer')` also deletes `(1, 'viewer')` and `(2,
'admin')`.
## What is the new behavior?
When there is more than one primary-key column, the delete uses the
tuple `IN` filter that pg-meta already supports:
```sql
delete from public.user_roles where (user_id, role_id) in ((1, 'admin'), (2, 'viewer'));
```
Single-column primary keys keep the same SQL as before. The batched
operation queue uses the same helper for its delete operations, so it is
covered as well.
Added unit tests for both the single and composite cases.
## Additional context
This is silent data loss on join tables, which are the most common place
for composite keys and for multi-row deletes.
## Summary
- The `NewScopedTokenSheet` tests were flaky on master. The 5s default
timeout was hit intermittently, most visibly in the "falls back to
Custom when a row diverges from the preset" test, plus some sibling
tests.
- Observed in unit-test runs on master. Reproduced locally by running
the file under heavy CPU load, where tests took 4.5-5s versus ~1s on an
idle machine.
- Fix: file-level `vi.setConfig({ testTimeout: 15_000 })`. This only
affects this file because vitest isolates test files.
## Testing
- Ran the file 5 times under the same CPU load. All 21 tests passed each
time.
## Problem
The Database Roles page groups `postgres` and the reserved
`supabase_privileged_role` alongside custom roles, while labeling the
other section as "Roles managed by Supabase." The `postgres` actions
menu also offers an enabled Delete action.
Fixes#51525.
## Solution
- Rename the sections to "Supabase service roles" and "Project database
roles," and explain that the latter includes the `postgres`
administrator and custom roles.
- Include `supabase_privileged_role` among the protected service roles.
- Disable Delete for the existing `SYSTEM_ROLES` list, using
`DropdownMenuItemTooltip` to explain the restriction. Keep the
`postgres` permission editor available.
- Add component regressions for grouping, system-role menus, permission
editing, and custom-role deletion confirmation.
## Validation
- Studio unit suite: 732 files passed; 8,343 tests passed and one
expected failure.
- `pnpm --filter studio typecheck` passed.
- `SKIP_ASSET_UPLOAD=1 pnpm build --filter=studio` passed.
- ESLint for changed files, the Studio lint ratchet, monorepo Prettier,
and Studio knip passed.
- Playwright smoke verification of the production Studio build with
synthetic API responses: grouping, disabled Delete and tooltip, keyboard
interaction, permission editing/cancellation, read-only service roles,
search, and custom-role deletion confirmation/cancellation.
## Review instructions
1. Open Database → Roles. Check the two section names and the
administrator description. If present, `supabase_privileged_role`
belongs to the protected service section.
2. Open the `postgres` actions menu. Delete is disabled, and hovering it
explains why.
3. Expand `postgres`. Its existing editable permission switches remain
enabled for a user who can edit roles.
4. Open a custom role's actions menu. Delete still opens the
confirmation dialog; cancel the dialog.
5. Run `pnpm --filter studio exec vitest run
tests/components/Database/Roles.test.tsx`.
## Checklist
- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
- [ ] If I wrote a new docs topic or edited an existing topic, I used
the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs
[style
guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide)
— not applicable; this changes Studio UI and tests.
<!-- claude-slack-attribution -->
_Requested by **Stojan Dimitrovski** · [Slack
thread](https://supabase.slack.com/archives/C022071RB2L/p1791475016175969?thread_ts=1791475016.175969&cid=C022071RB2L)_
## Problem
The Management API accepts any OIDC issuer for third-party auth (`POST
/v1/projects/{ref}/config/auth/third-party-auth` with
`oidc_issuer_url`), but the Dashboard only offers Firebase, Clerk,
WorkOS, Auth0 and Amazon Cognito. Users with another OIDC provider have
no way to register it from the Dashboard.
Studio also hides any issuer it does not recognize: `IntegrationCard`
returns `null` for the `custom` type, so an issuer added through the API
never shows up in the list and cannot be deleted from the Dashboard.
## Solution
- Add a "Custom OIDC" item to the Add provider menu on the Third-Party
Auth page.
- New `CreateCustomOidcDialog` with a single required Issuer URL field.
It reuses `useCreateThirdPartyAuthIntegrationMutation`, so query
invalidation and the error toast behave like the other providers. The
URL is sent as typed after trimming whitespace.
- New `isValidOidcIssuerUrl` helper in `ThirdPartyAuthForm.utils.ts`:
the value must be an `https://` URL with a host, no query string, no
fragment, and no embedded credentials.
- The dialog copy is kept short: a one-line description and a single
helper line noting that signing keys are found at the issuer's
`/.well-known/openid-configuration`.
- `IntegrationCard` now renders custom issuers (badge-check icon,
"Custom OIDC" label, Issuer URL, Delete integration) instead of hiding
them. Delete uses the existing flow and permission check.
- The `custom` label changes from "Custom" to "Custom OIDC".
- New docs page
`apps/docs/content/guides/auth/third-party/custom-oidc.mdx` (served at
`/docs/guides/auth/third-party/custom-oidc`). It covers the Dashboard
flow, the Management API alternative, the `role` claim, client setup,
and RLS. It is linked from the third-party auth overview and added to
the docs sidebar. This supersedes the draft docs PR #51611.
Not included: UI for `jwks_url` and `custom_jwks`. The page and menu
keep the same feature gate (`authentication:third_party_auth`) and the
same permission checks as the other providers.
Before: the Add provider menu lists five providers. An issuer that is
not one of them is registered only through the API and does not appear
in the Dashboard.
After: the menu has a sixth item, Custom OIDC. It opens a dialog that
takes an issuer URL, rejects non-HTTPS values with an inline message,
and registers the issuer. The new provider appears in the list and can
be deleted from there.
## Review instructions
1. Open a project's Authentication > Third-Party Auth page and open Add
provider. Check for the new Custom OIDC item.
2. Enter `http://example.com` and submit. The field shows an inline
error and no request is sent.
3. Enter a valid HTTPS issuer URL that serves an OIDC discovery document
and submit. A success toast appears and the provider shows in the list
as Custom OIDC with its Issuer URL.
4. Enter an issuer the API rejects. The API error shows in the existing
error toast and the dialog stays open.
5. Delete the provider with Delete integration and confirm it disappears
from the list.
6. Read the new docs page and check that its Dashboard steps match the
UI.
Local checks:
- `vitest --run components/interfaces/Auth/ThirdPartyAuthForm`: 20 tests
pass (validation helper unit tests plus dialog component tests with
MSW).
- `tsc --noEmit` in `apps/studio`: no new errors.
- ESLint on the directory: no warnings in the new or changed code.
- Prettier is clean on the changed files, including the docs page. The
typos check is clean on the docs page and overview.
I did not run the dialog in a browser against a real project, so there
are no screenshots. The docs app was not built or rendered locally.
## Checklist
- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
- [x] If I wrote a new docs topic or edited an existing topic, I used
the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs
[style
guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide)
🤖 Generated with [Claude
Code](https://claude.ai/code/session_011HY2zckzPLcvXd28JPSqBe)
https://claude.ai/code/session_011HY2zckzPLcvXd28JPSqBe
---
_Generated by [Claude
Code](https://claude.ai/code/session_011HY2zckzPLcvXd28JPSqBe)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Each assistant tool's identity and contract is currently written in up
to nine places that nothing ties together at compile time. This PR adds
a contract/server split and migrates `update_notebook`, the tool with
the most copies, as the first proof of the design.
- **Contract** (`lib/ai/tools/update-notebook.ts`): isomorphic file with
the input and output schemas, approval flag, minimum opt-in level, and
`toModelOutput`, the single rule for what the model may see.
- **Server** (`update-notebook.server.ts`): the executable tool, built
with `approvalGatedToolFromContract`.
- **Registry** (`registry.ts`): collects contracts, derives a typed
`StudioUIMessage`, and provides `sanitizeContractPart` for history
replay.
- **Shared types** (`types.ts`): `ToolContract`, `ToolContext`,
`ToolFactory`, plus a single `getAuthHeaders` replacing three variants.
- **Approval gate** (`approval-gate.ts`): approval-gated tools get
`acceptUntrustedSql` / `acceptUntrustedLogsSql` through a gate passed to
`execute`, and `*.server.ts` files can't import the raw functions
(ESLint).
- **Opt-in level**: `update_notebook` is gated by its contract's
`minOptInLevel`. `TOOL_CATEGORY_MAP` is now typed against `ToolName` and
excludes migrated tools, which exposed `get_active_incidents` missing
from the name enum.
Removed duplicates: the `update_notebook` sanitizer in
`tool-sanitizer.ts`, the client-side `stripNotebookSnapshot`, and the
copied update schemas in `Message.utils.ts`.
## Behavior changes
- The client no longer strips `previous_content` from `update_notebook`
outputs before sending history, so requests carry the previous notebook
snapshot. The server still strips it before the model sees it.
- On history replay, an `update_notebook` output that doesn't match its
schema is dropped instead of passed through minus `previous_content`.
## Testing
- New `registry.test.ts` (typed parts, replay sanitizer) and a contract
case in `tool-filter.test.ts`.
- Studio typecheck, lint ratchet, knip, Prettier and the affected vitest
suites pass.
## Context
This is largely just a mechanical change here:
Opting to use lucide icons with the `Icon` suffix across all icon
imports within `studio`
- so that it’s clearer which components are icons + reduces the
possibility of name collision with our own components
- e.g <User /> vs <UserIcon />
Also added a eslint rule for this:
<img width="1069" height="167" alt="image"
src="https://github.com/user-attachments/assets/7f36f443-5ca2-4bf6-8d22-86e774513bfc"
/>
And updated `AGENTS.md` as such:
```
Icons come from `lucide-react`, always imported with the `Icon` suffix (`import { CheckIcon, Trash2Icon } from 'lucide-react'`) — never the bare names (`Check`) or `Check as CheckIcon` aliases (lint-enforced).
```
## Problem
Closes#31221.
The applications and shared UI packages still import the former
`framer-motion` package instead of Motion's React entry point.
## Solution
Replace runtime imports, type imports, and three test mocks with
`motion/react` across the consuming workspaces.
Replace the seven direct dependency declarations with `motion:
^11.18.2`.
Preserve the existing animation engine version and all other dependency
resolutions, including the asynchronous `domMax` feature loader.
Historical blog examples remain as published; `framer-motion` remains a
transitive implementation dependency of Motion.
## Review instructions
1. Run `pnpm install --frozen-lockfile` and check the seven dependency
replacements in the lockfile.
2. Open the WWW Auth page and Docs homepage at desktop and mobile
widths, with normal and reduced motion preferences.
3. Open the mobile navigation, expand Product on WWW, and close the menu
using Escape and the close button.
4. Check the existing animation components, shared form validation, and
Studio tests for unchanged behavior.
Validation:
- Studio: 731 test files, 8,334 passing tests and one expected failure,
with `--maxWorkers=4`.
- Shared UI, UI patterns, WWW, and UI library: 615 passing tests.
- `pnpm run typecheck --concurrency=1`: all 16 repository tasks pass.
- All six application source lint scripts and lint ratchets pass;
generated Docs example copies are excluded from local lint.
- Repository Prettier source check, lockfile formatting, and case hazard
check pass.
- WWW: 10 existing end-to-end checks across nine pages.
- Docs: 5 existing guide link and accessibility checks across Database
and Auth.
- WWW and Docs mobile navigation pass at desktop/mobile sizes with
normal/reduced motion; SQL-to-REST translation tabs exercise the
asynchronous feature loader without browser errors.
- Frozen-lockfile installation passes; all existing package and snapshot
resolutions are unchanged.
- Learn and UI library command palettes open, filter results, and close
with Escape without browser errors.
- Independent code review finds no actionable defects.
- `npm run build -- --filter=www --filter=docs --filter=studio
--filter=learn --filter=library --concurrency=1 --continue=always`: all
five affected applications build successfully (9 tasks including
prerequisites); asset uploads disabled for local validation.
## Checklist
- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
- [x] If I wrote a new docs topic or edited an existing topic, I used
the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs
[style
guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide)
(no docs topics changed)
---------
Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
## Problem
The pipeline wizard in #49243 needs publication, initial sync and
replication settings together on its data step.
## Solution
Compose `PipelineDataSection` from the existing controls and use it in
preview-enabled creation. It includes the publication selector,
initial-sync controls and data advanced settings, including BigQuery
table layout.
This preserves the grouping from #51570. Preview off and editing keep
the existing layout. Validation and creation behaviour stay the same; no
wizard routing is added. Stacked on #51570.
## Review instructions
1. Open the [Pipelines
preview](https://studio-staging-git-dnywh-featpipeline-data-section-supabase.vercel.app/dashboard/project/_/database/pipelines),
select a test project with Pipelines enabled and go to **Database →
Pipelines**. Enable **Pipeline creation wizard** in Feature Previews,
then click **Add pipeline**. Confirm **Data** follows **Destination
connection**, with publication and initial sync above its **Advanced
settings**.
2. Select a publication and change the initial-sync option. For
selected-table options, confirm the publication tables are available.
Click **New publication** in the publication selector and confirm the
existing creation panel opens, then cancel it.
3. Expand **Advanced settings** under **Data**. Confirm replication
settings appear, plus table layout for BigQuery, without connection pool
size, staleness or role. Change **Batch wait time**, collapse and reopen
the accordion, and confirm the value remains. Check desktop and phone
widths. Cancel and discard changes; no credentials or pipeline creation
are needed.
4. Turn the preview off and reopen **Add pipeline**. Confirm the
original layout remains. With the preview on, edit an existing pipeline
and confirm its original layout also remains.
## Problem
The pipeline wizard in #49243 needs connection settings before table
selection.
## Solution
With **Pipeline creation wizard** enabled, rearrange the creation sheet
into destination selection, **Destination connection**, then **Data**.
Group name, region, credentials and connection advanced settings
together. Keep publication, initial sync, replication settings and
BigQuery table layout in the data section.
Preview off and editing keep the existing layout. Validation and
creation behaviour stay the same. This still uses the sheet; no wizard
routing is added. Stacked on #51568.
| Feature Preview Off | Feature Preview On |
| --- | --- |
| <img width="1272" height="2536" alt="CleanShot 2026-10-13 at 13 16
36@2x"
src="https://github.com/user-attachments/assets/ebad1082-b9ae-4f5e-91ad-08638ea2b032"
/> | <img width="1244" height="2538" alt="CleanShot 2026-10-13 at 13 17
20@2x"
src="https://github.com/user-attachments/assets/b5400b31-d60e-44e7-a4d6-cca1732b7974"
/> |
| _One “Advanced settings” at the bottom of the sheet that combines
everything. Destination details handles Publication and Initial sync._ |
_An “Advanced settings” accordion under BigQuery settings. Another
“Advanced settings” under Data (clipped above). Data contains fields for
Publication and Initial sync._ |
## Review instructions
1. Open the [Pipelines
preview](https://studio-staging-git-dnywh-featpipeline-connectio-6d0876-supabase.vercel.app/dashboard/project/_/database/pipelines),
select a test project with Pipelines enabled and click **Add pipeline**.
Enable **Pipeline creation wizard** in Feature Previews, then reopen the
sheet. Confirm the order is destination cards → connection → data.
2. With BigQuery selected, expand **Advanced settings** under
**Destination connection**. Confirm it contains pool size and staleness.
Expand **Advanced settings** under **Data** below publication and
initial sync. Confirm it contains replication settings and table layout,
without duplicate connection fields. Change a value and collapse/reopen
the accordion; confirm it remains.
3. If available, select DuckLake or Snowflake. Confirm their connection
accordion contains **Pool size** or **Role**, respectively. ClickHouse
and Analytics Bucket have no connection accordion. Check desktop and
phone widths. Cancel and discard changes; no credentials or pipeline
creation are needed.
4. Turn the preview off and reopen **Add pipeline**. Confirm the
original layout returns. With the preview on, edit an existing pipeline
and confirm it also keeps the original layout.
## Problem
The pipeline creation wizard in #49243 needs a destination selection
step that can be reviewed before wizard routing lands.
## Solution
Add `PipelineDestinationSelection` with BigQuery, DuckLake, Snowflake,
ClickHouse and deprecated Analytics Bucket radio options, using Studio’s
existing destination logos. Show it in the creation sheet only when
**Pipeline creation wizard** is enabled in Feature Previews and allowed
by ConfigCat. Preview off and editing retain the existing dropdown.
This is an incremental preview: creation still uses the sheet. No wizard
routing or design-system changes are included.
| Feature Preview Off | Feature Preview On |
| --- | --- |
| <img width="1274" height="1316" alt="CleanShot 2026-10-13 at 10 39
24@2x"
src="https://github.com/user-attachments/assets/433e4fb1-5fa2-4dc3-982e-0aee6b266015"
/> | <img width="1250" height="1890" alt="CleanShot 2026-10-13 at 10 53
09@2x"
src="https://github.com/user-attachments/assets/033d7e43-1a57-4d61-90e8-9048e3560909"
/> |
## Review instructions
1. Open the [preview to Database →
Pipelines](https://studio-staging-git-dnywh-featpipeline-destinati-54dc7d-supabase.vercel.app/dashboard/project/_/database/pipelines),
select a test project with Pipelines enabled and click **Add pipeline**.
With **Pipeline creation wizard** off, confirm the existing destination
dropdown appears.
2. Close the sheet, enable **Pipeline creation wizard** in Feature
Previews and reopen **Add pipeline**. Confirm radio cards replace the
dropdown, with BigQuery selected initially and only destinations
available to your project shown.
3. If Analytics Bucket is available, confirm its card has a
**DEPRECATED** badge. Select it and confirm the bucket fields appear and
the description below says **Destination type cannot be changed after
creation. Analytics Bucket is deprecated.** Click another destination.
Confirm only one card is selected and the connection fields below switch
to that destination. Use arrow keys to move focus and Space to select a
destination. Check at desktop and phone widths. Cancel and discard
changes; no credentials or pipeline creation are needed.
4. With the preview still on, open an existing pipeline for editing.
Confirm its destination dropdown remains locked. Turn the preview off
and reopen **Add pipeline** to confirm the original dropdown returns.
## What
Fixes two bugs found while writing component tests for the platform
webhooks page (follow-up to FE-3749):
1. The webhooks API nests error details under an `error` key (`{ error:
{ code, message } }`), but the shared `handleError` only reads a
top-level `message`. This meant the friendly rate-limit message ("wait a
minute and try again") never actually fired, it always fell back to a
generic error. Fixed with a local unwrapping `handleError` in
`platform-webhooks-fetchers.ts`.
2. Every mutation handler on the page (`mutateAsync` for
create/update/delete/regenerate-secret/send-test-event/retry) left the
re-thrown error from React Query uncaught, producing an unhandled
promise rejection on every failure, even though the error toast itself
was already correct. Fixed by wrapping each call in try/catch.
## Why
Found while writing the stacked test PR for FE-3749; filed as
[FE-4620](https://linear.app/supabase/issue/FE-4620/two-webhook-error-handling-bugs-found-while-writing-retryrate-limit)
since it's a correctness fix independent of the test work.
## Testing
Manually verified both fixes on localhost: rate-limit retry now shows
the friendly message, and no `Uncaught (in promise)` errors appear in
the console on any mutation failure.
## Context
Shows the production branch as part of the branching empty state (Shifts
`BranchingEmptyState` into `Overview`)
It's so that users can still then reach the "View logs" button for the
production branch which was otherwise hidden
<img width="1214" height="531" alt="image"
src="https://github.com/user-attachments/assets/3bc702f2-950b-4d9e-8b4f-78149af9ed82"
/>
## Problem
When uploading a file with same name of already existing file, the ui
automatically creates a new file with incremental number and doesn't
consider the scenario of giving the possibility of storing the new
upload as a version of the already existing file.
## Solution
Provide modal to chose the behaviour when name conflict happens when
uploading a file:
- Replace existing file (same file with version history)
- Keep both (new file with incremental numbering)
https://github.com/user-attachments/assets/ee192be9-4087-4e14-89f1-1c82acf550cd
Manage edge case of clashes with files created when bucket had
versioning enabled (so with `is_versioned` flag to true) and clashing
upload happens when bucket is in SUSPENDED state.
https://github.com/user-attachments/assets/00694c77-07c1-405e-bd54-bdf0bf99d274
Unrelated fix:
- add "Delete permanently" action when selecting multiple files with
versioning enabled
<img width="510" height="203" alt="Screenshot 2026-10-13 at 15 59 27"
src="https://github.com/user-attachments/assets/5692fc23-2fd3-4259-9de2-3932aecbccd0"
/>