From 74c75c6d3a1c3934cd3d6622d3b8efd5f4594e27 Mon Sep 17 00:00:00 2001
From: Jordi Enric <37541088+jordienr@users.noreply.github.com>
Date: Tue, 14 Jul 2026 18:24:30 +0200
Subject: [PATCH 1/7] fix(studio): cap errors-since-last-deploy log range to
24h DEBUG-173 (#47911)
## Problem
The edge function overview's "Errors since last deploy" panel queried
ClickHouse from the deploy timestamp to now, with no upper bound. When a
function had not been redeployed in a long time, this produced an
unbounded query range, which is suspected to have caused a recent uptime
incident.
## Fix
`getSinceLastDeployLogRange` now clamps the query start to at most 24
hours before now, shared by all three queries this panel issues
(invocation list, invocation count, runtime logs). The section title was
also updated from "Errors since last deploy" to "Errors in the last 24h"
to reflect the new bound.
## How to test
- Open an edge function's overview page for a function that was deployed
more than 24 hours ago
- Confirm the "Errors in the last 24h" panel loads without an
excessively large query range
- Expected result: the panel only queries the last 24 hours of logs
regardless of how old the last deploy was
- Run `pnpm test:studio -- EdgeFunctionRecentErrors.utils` and confirm
the range-clamping tests pass
## Summary by CodeRabbit
- **New Features**
- Updated the Edge Function errors section to show errors from the last
24 hours.
- **Bug Fixes**
- Limited error log searches to a maximum 24-hour window, preventing
outdated results from appearing.
- Improved handling of error time ranges when the last deployment
occurred earlier than the available window.
---
.../EdgeFunctionOverview/EdgeFunctionRecentErrors.tsx | 2 +-
.../EdgeFunctionRecentErrors.utils.test.ts | 7 +++++++
.../EdgeFunctionRecentErrors.utils.ts | 10 +++++++---
3 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.tsx
index 8333ec6445a5c..9233c5e4edb6e 100644
--- a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.tsx
+++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.tsx
@@ -204,7 +204,7 @@ export const EdgeFunctionRecentErrors = ({
- Errors since last deploy
+ Errors in the last 24h
{
diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.utils.ts b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.utils.ts
index a52fa09cdb428..4fe81af6f92a4 100644
--- a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.utils.ts
+++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.utils.ts
@@ -127,17 +127,21 @@ export const toIsoTimestamp = (value?: string | number) => {
return Number.isNaN(date.valueOf()) ? undefined : date.toISOString()
}
+export const SINCE_LAST_DEPLOY_MAX_RANGE_MS = 24 * 60 * 60 * 1000
+
export const getSinceLastDeployLogRange = (updatedAt?: string | number, now: Date = new Date()) => {
const isoTimestampStart = toIsoTimestamp(updatedAt)
if (!isoTimestampStart) return {}
const startDate = new Date(isoTimestampStart)
const normalizedNow = new Date(now)
- const endDate = Number.isNaN(normalizedNow.valueOf()) ? new Date() : normalizedNow
+ const nowDate = Number.isNaN(normalizedNow.valueOf()) ? new Date() : normalizedNow
+ const endDate = new Date(Math.max(startDate.valueOf(), nowDate.valueOf()))
+ const earliestAllowedStart = endDate.valueOf() - SINCE_LAST_DEPLOY_MAX_RANGE_MS
return {
- isoTimestampStart,
- isoTimestampEnd: new Date(Math.max(startDate.valueOf(), endDate.valueOf())).toISOString(),
+ isoTimestampStart: new Date(Math.max(startDate.valueOf(), earliestAllowedStart)).toISOString(),
+ isoTimestampEnd: endDate.toISOString(),
}
}
From 7a60819233666f6368a58a11acedb8e84f2fb845 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 14 Jul 2026 12:32:48 -0400
Subject: [PATCH 2/7] chore(deps): bump actions/checkout from 4.3.0 to 7.0.0
(#47925)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.3.0
to 7.0.0.
Release notes
Sourced from actions/checkout's
releases .
v7.0.0
What's Changed
New Contributors
Full Changelog : https://github.com/actions/checkout/compare/v6.0.3...v7.0.0
v6.0.3
What's Changed
New Contributors
Full Changelog : https://github.com/actions/checkout/compare/v6...v6.0.3
v6.0.2
What's Changed
Full Changelog : https://github.com/actions/checkout/compare/v6.0.1...v6.0.2
v6.0.1
What's Changed
Full Changelog : https://github.com/actions/checkout/compare/v6...v6.0.1
v6.0.0
What's Changed
... (truncated)
Changelog
Sourced from actions/checkout's
changelog .
Changelog
v7.0.0
v6.0.3
v6.0.2
v6.0.1
v6.0.0
v5.0.1
v5.0.0
v4.3.1
v4.3.0
v4.2.2
v4.2.1
... (truncated)
Commits
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Charis <26616127+charislam@users.noreply.github.com>
---
.github/workflows/ai-tests.yml | 2 +-
.github/workflows/authorize-vercel-deploys.yml | 2 +-
.github/workflows/autofix_linters.yml | 4 ++--
.github/workflows/avoid-typos.yml | 2 +-
.github/workflows/braintrust-evals.yml | 4 ++--
.github/workflows/braintrust-preview-scorers-cleanup.yml | 2 +-
.github/workflows/braintrust-preview-scorers-deploy.yml | 2 +-
.github/workflows/braintrust-scorers-deploy.yml | 2 +-
.github/workflows/dashboard-pr-reminder.yml | 2 +-
.github/workflows/docs-js-libs-update.yml | 2 +-
.github/workflows/docs-last-changed.yml | 2 +-
.github/workflows/docs-lint-v2-scheduled.yml | 2 +-
.github/workflows/docs-lint-v2.yml | 2 +-
.github/workflows/docs-mgmt-api-update.yml | 2 +-
.github/workflows/docs-sync-auto-troubleshooting.yml | 4 ++--
.github/workflows/docs-sync-troubleshooting.yml | 2 +-
.github/workflows/docs-sync.yml | 2 +-
.github/workflows/docs-tests-smoke.yml | 2 +-
.github/workflows/docs-tests.yml | 2 +-
.github/workflows/fix-typos.yml | 2 +-
.github/workflows/og_images.yml | 2 +-
.github/workflows/pg-meta-tests.yml | 2 +-
.github/workflows/prettier.yml | 2 +-
.github/workflows/publish_image.yml | 2 +-
.github/workflows/search.yml | 2 +-
.github/workflows/self-host-tests-smoke.yml | 2 +-
.github/workflows/studio-docker-build.yml | 2 +-
.github/workflows/studio-e2e-test.yml | 4 ++--
.github/workflows/studio-lint-ratchet-decrease.yml | 2 +-
.github/workflows/studio-lint-ratchet.yml | 2 +-
.github/workflows/studio-unit-tests.yml | 4 ++--
.github/workflows/typecheck.yml | 2 +-
.github/workflows/ui-patterns-tests.yml | 2 +-
.github/workflows/ui-tests.yml | 4 ++--
.github/workflows/update-js-libs.yml | 2 +-
.github/workflows/update-ssr.yml | 2 +-
.github/workflows/www-tests.yml | 2 +-
.github/workflows/zizmor.yml | 2 +-
38 files changed, 44 insertions(+), 44 deletions(-)
diff --git a/.github/workflows/ai-tests.yml b/.github/workflows/ai-tests.yml
index 7594f14fa9412..f8db88a83dcf3 100644
--- a/.github/workflows/ai-tests.yml
+++ b/.github/workflows/ai-tests.yml
@@ -32,7 +32,7 @@ jobs:
working-directory: ./packages/ai-commands
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/authorize-vercel-deploys.yml b/.github/workflows/authorize-vercel-deploys.yml
index 2fce600a211bc..747cddbf5d947 100644
--- a/.github/workflows/authorize-vercel-deploys.yml
+++ b/.github/workflows/authorize-vercel-deploys.yml
@@ -23,7 +23,7 @@ jobs:
steps:
# Checkout the master branch from the supabase repo and run that script to authorize Vercel deploys
- name: Check out repo
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: master
diff --git a/.github/workflows/autofix_linters.yml b/.github/workflows/autofix_linters.yml
index 5f3e16d81f5a9..4ddc87ba5629d 100644
--- a/.github/workflows/autofix_linters.yml
+++ b/.github/workflows/autofix_linters.yml
@@ -25,7 +25,7 @@ jobs:
- name: Calculate number of commits
run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_ENV}"
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
@@ -87,7 +87,7 @@ jobs:
private-key: ${{ secrets.GH_AUTOFIX_PRIVATE_KEY }}
permission-contents: write
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
diff --git a/.github/workflows/avoid-typos.yml b/.github/workflows/avoid-typos.yml
index 1102aedef595d..8d171f231b05d 100644
--- a/.github/workflows/avoid-typos.yml
+++ b/.github/workflows/avoid-typos.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Check out code.
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: misspell
diff --git a/.github/workflows/braintrust-evals.yml b/.github/workflows/braintrust-evals.yml
index 02c443fce5481..dc903993293f7 100644
--- a/.github/workflows/braintrust-evals.yml
+++ b/.github/workflows/braintrust-evals.yml
@@ -26,7 +26,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
@@ -62,7 +62,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
diff --git a/.github/workflows/braintrust-preview-scorers-cleanup.yml b/.github/workflows/braintrust-preview-scorers-cleanup.yml
index abe53efb2d497..0308dd685e508 100644
--- a/.github/workflows/braintrust-preview-scorers-cleanup.yml
+++ b/.github/workflows/braintrust-preview-scorers-cleanup.yml
@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha }}
diff --git a/.github/workflows/braintrust-preview-scorers-deploy.yml b/.github/workflows/braintrust-preview-scorers-deploy.yml
index ca3de102b5cbc..a0127505bdbcc 100644
--- a/.github/workflows/braintrust-preview-scorers-deploy.yml
+++ b/.github/workflows/braintrust-preview-scorers-deploy.yml
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
diff --git a/.github/workflows/braintrust-scorers-deploy.yml b/.github/workflows/braintrust-scorers-deploy.yml
index 79aec9b7448f4..8298ba28f917d 100644
--- a/.github/workflows/braintrust-scorers-deploy.yml
+++ b/.github/workflows/braintrust-scorers-deploy.yml
@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
diff --git a/.github/workflows/dashboard-pr-reminder.yml b/.github/workflows/dashboard-pr-reminder.yml
index d259f80d0d959..0ea53f030ebe4 100644
--- a/.github/workflows/dashboard-pr-reminder.yml
+++ b/.github/workflows/dashboard-pr-reminder.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/docs-js-libs-update.yml b/.github/workflows/docs-js-libs-update.yml
index c2f5b64be5c35..a423b46502b67 100644
--- a/.github/workflows/docs-js-libs-update.yml
+++ b/.github/workflows/docs-js-libs-update.yml
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: master
diff --git a/.github/workflows/docs-last-changed.yml b/.github/workflows/docs-last-changed.yml
index 7975c1ce591e7..3e138de62b3d0 100644
--- a/.github/workflows/docs-last-changed.yml
+++ b/.github/workflows/docs-last-changed.yml
@@ -23,7 +23,7 @@ jobs:
steps:
- name: Check out repo
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
diff --git a/.github/workflows/docs-lint-v2-scheduled.yml b/.github/workflows/docs-lint-v2-scheduled.yml
index 6ba2b37e3712d..16ca6e7596d0f 100644
--- a/.github/workflows/docs-lint-v2-scheduled.yml
+++ b/.github/workflows/docs-lint-v2-scheduled.yml
@@ -15,7 +15,7 @@ jobs:
lint-all:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: true
diff --git a/.github/workflows/docs-lint-v2.yml b/.github/workflows/docs-lint-v2.yml
index 96e1411447190..6f78808236c46 100644
--- a/.github/workflows/docs-lint-v2.yml
+++ b/.github/workflows/docs-lint-v2.yml
@@ -28,7 +28,7 @@ jobs:
name: supa-mdx-lint
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
diff --git a/.github/workflows/docs-mgmt-api-update.yml b/.github/workflows/docs-mgmt-api-update.yml
index 087bff77fef0b..a455e489a03d8 100644
--- a/.github/workflows/docs-mgmt-api-update.yml
+++ b/.github/workflows/docs-mgmt-api-update.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: master
diff --git a/.github/workflows/docs-sync-auto-troubleshooting.yml b/.github/workflows/docs-sync-auto-troubleshooting.yml
index 0b01322a16166..3e6ada03f6f2f 100644
--- a/.github/workflows/docs-sync-auto-troubleshooting.yml
+++ b/.github/workflows/docs-sync-auto-troubleshooting.yml
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Checkout supabase/supabase
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: true
@@ -45,7 +45,7 @@ jobs:
permission-contents: read
- name: Checkout supabase/troubleshooting
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.app-token.outputs.token }}
diff --git a/.github/workflows/docs-sync-troubleshooting.yml b/.github/workflows/docs-sync-troubleshooting.yml
index 43bd118c0f4e3..468b0f3fce138 100644
--- a/.github/workflows/docs-sync-troubleshooting.yml
+++ b/.github/workflows/docs-sync-troubleshooting.yml
@@ -24,7 +24,7 @@ jobs:
SUPABASE_SECRET_KEY: ${{ secrets.SEARCH_SUPABASE_SERVICE_ROLE_KEY }}
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/docs-sync.yml b/.github/workflows/docs-sync.yml
index 2adb6ea8b9b6f..3c8f90a05c995 100644
--- a/.github/workflows/docs-sync.yml
+++ b/.github/workflows/docs-sync.yml
@@ -26,7 +26,7 @@ jobs:
SUPABASE_SECRET_KEY: ${{ secrets.SEARCH_SUPABASE_SERVICE_ROLE_KEY }}
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/docs-tests-smoke.yml b/.github/workflows/docs-tests-smoke.yml
index 3a85c188ea973..2fc1579b41632 100644
--- a/.github/workflows/docs-tests-smoke.yml
+++ b/.github/workflows/docs-tests-smoke.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/docs-tests.yml b/.github/workflows/docs-tests.yml
index bbaddc5fb274d..5acd99674a21e 100644
--- a/.github/workflows/docs-tests.yml
+++ b/.github/workflows/docs-tests.yml
@@ -23,7 +23,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/fix-typos.yml b/.github/workflows/fix-typos.yml
index ae2f45cf87daf..08cdde4198055 100644
--- a/.github/workflows/fix-typos.yml
+++ b/.github/workflows/fix-typos.yml
@@ -16,7 +16,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: master
diff --git a/.github/workflows/og_images.yml b/.github/workflows/og_images.yml
index 99c88ddaed552..7ba7045f52998 100644
--- a/.github/workflows/og_images.yml
+++ b/.github/workflows/og_images.yml
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Check out repo
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
diff --git a/.github/workflows/pg-meta-tests.yml b/.github/workflows/pg-meta-tests.yml
index 1119cd57f9890..f46c0360cd1d3 100644
--- a/.github/workflows/pg-meta-tests.yml
+++ b/.github/workflows/pg-meta-tests.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml
index 3e74a745d2cb4..6b8a5b6e4ed7a 100644
--- a/.github/workflows/prettier.yml
+++ b/.github/workflows/prettier.yml
@@ -18,7 +18,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Check out repo
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/publish_image.yml b/.github/workflows/publish_image.yml
index 817541de8d35c..e40a023c52ccb 100644
--- a/.github/workflows/publish_image.yml
+++ b/.github/workflows/publish_image.yml
@@ -72,7 +72,7 @@ jobs:
outputs:
image_digest: ${{ steps.build.outputs.digest }}
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- id: meta
diff --git a/.github/workflows/search.yml b/.github/workflows/search.yml
index f75392eb7da70..be8a466334b42 100644
--- a/.github/workflows/search.yml
+++ b/.github/workflows/search.yml
@@ -40,7 +40,7 @@ jobs:
steps:
- name: Check out repo
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/self-host-tests-smoke.yml b/.github/workflows/self-host-tests-smoke.yml
index 983a182d52d4e..455d68c81fe56 100644
--- a/.github/workflows/self-host-tests-smoke.yml
+++ b/.github/workflows/self-host-tests-smoke.yml
@@ -23,7 +23,7 @@ jobs:
config: [default, logs, envoy, rustfs, envoy-rustfs]
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/studio-docker-build.yml b/.github/workflows/studio-docker-build.yml
index 221ee2a9252a1..409d2b9edbdd1 100644
--- a/.github/workflows/studio-docker-build.yml
+++ b/.github/workflows/studio-docker-build.yml
@@ -18,7 +18,7 @@ jobs:
name: 'Studio Docker Build'
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
diff --git a/.github/workflows/studio-e2e-test.yml b/.github/workflows/studio-e2e-test.yml
index 6ba15479c180d..7840b6e618150 100644
--- a/.github/workflows/studio-e2e-test.yml
+++ b/.github/workflows/studio-e2e-test.yml
@@ -35,7 +35,7 @@ jobs:
STUDIO_FRAMEWORK: ${{ matrix.framework }}
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@@ -156,7 +156,7 @@ jobs:
contents: write
pull-requests: write
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Use Node.js
diff --git a/.github/workflows/studio-lint-ratchet-decrease.yml b/.github/workflows/studio-lint-ratchet-decrease.yml
index 136d778569a80..937d25ed4b88a 100644
--- a/.github/workflows/studio-lint-ratchet-decrease.yml
+++ b/.github/workflows/studio-lint-ratchet-decrease.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/studio-lint-ratchet.yml b/.github/workflows/studio-lint-ratchet.yml
index 16b0553b9d359..51657bade07b2 100644
--- a/.github/workflows/studio-lint-ratchet.yml
+++ b/.github/workflows/studio-lint-ratchet.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/studio-unit-tests.yml b/.github/workflows/studio-unit-tests.yml
index e5571f957fa6d..d499d32112c48 100644
--- a/.github/workflows/studio-unit-tests.yml
+++ b/.github/workflows/studio-unit-tests.yml
@@ -31,7 +31,7 @@ jobs:
tests_ran: ${{ steps.filter.outputs.relevant }}
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
@@ -82,7 +82,7 @@ jobs:
continue-on-error: true
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml
index e6faaa8066146..538d6eb3c2806 100644
--- a/.github/workflows/typecheck.yml
+++ b/.github/workflows/typecheck.yml
@@ -22,7 +22,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
diff --git a/.github/workflows/ui-patterns-tests.yml b/.github/workflows/ui-patterns-tests.yml
index c1a20d90fa932..be38c2332d827 100644
--- a/.github/workflows/ui-patterns-tests.yml
+++ b/.github/workflows/ui-patterns-tests.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 524a6642a2fd8..aff844ce3d13f 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -28,7 +28,7 @@ jobs:
filters: |
relevant:
- 'packages/ui/**'
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
if: steps.filter.outputs.relevant == 'true'
with:
persist-credentials: false
@@ -71,7 +71,7 @@ jobs:
continue-on-error: true
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/update-js-libs.yml b/.github/workflows/update-js-libs.yml
index d36c1549203fa..c0a59cc1fb56a 100644
--- a/.github/workflows/update-js-libs.yml
+++ b/.github/workflows/update-js-libs.yml
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: master
diff --git a/.github/workflows/update-ssr.yml b/.github/workflows/update-ssr.yml
index 09cb1a9b289f4..542b8c041d1c5 100644
--- a/.github/workflows/update-ssr.yml
+++ b/.github/workflows/update-ssr.yml
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: master
diff --git a/.github/workflows/www-tests.yml b/.github/workflows/www-tests.yml
index 95410006eeb2d..bbc838989e421 100644
--- a/.github/workflows/www-tests.yml
+++ b/.github/workflows/www-tests.yml
@@ -24,7 +24,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml
index f6df8c9d8fd05..328eade145bca 100644
--- a/.github/workflows/zizmor.yml
+++ b/.github/workflows/zizmor.yml
@@ -25,7 +25,7 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
sparse-checkout: |
From c9d7f00b8e32656576ef54d12b66e0645a145a97 Mon Sep 17 00:00:00 2001
From: Jordi Enric <37541088+jordienr@users.noreply.github.com>
Date: Tue, 14 Jul 2026 18:49:14 +0200
Subject: [PATCH 3/7] feat(studio): add Go to API Keys command menu action
(#47916)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
- Add a "Go to API Keys" action under the command menu's Navigate
section, linking to `/project/[ref]/settings/api-keys`
## Test plan
- [ ] Open command menu, search "api key", confirm "Go to API Keys"
navigates to the settings page
## Summary by CodeRabbit
* **New Features**
* Added a command-menu option to quickly navigate to the project’s API
Keys settings page.
---
.../interfaces/App/CommandMenu/ApiKeys.tsx | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx b/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx
index 82fe7b2cbb21e..d8ad22c076374 100644
--- a/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx
+++ b/apps/studio/components/interfaces/App/CommandMenu/ApiKeys.tsx
@@ -160,4 +160,17 @@ export function useApiKeysCommands() {
sectionMeta: { priority: 3 },
}
)
+
+ useRegisterCommands(
+ COMMAND_MENU_SECTIONS.NAVIGATE,
+ [
+ {
+ id: 'nav-api-keys',
+ name: 'Go to API Keys',
+ route: `/project/${ref}/settings/api-keys`,
+ icon: () => ,
+ },
+ ],
+ { enabled: !!project }
+ )
}
From 05d2f3866110e3ad63dc8f4683f4bb745c34de10 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 14 Jul 2026 12:57:33 -0400
Subject: [PATCH 4/7] chore(deps): bump actions/github-script from 7.1.0 to
9.0.0 (#47929)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps [actions/github-script](https://github.com/actions/github-script)
from 7.1.0 to 9.0.0.
Release notes
Sourced from actions/github-script's
releases .
v9.0.0
New features:
getOctokit factory function —
Available directly in the script context. Create additional
authenticated Octokit clients with different tokens for multi-token
workflows, GitHub App tokens, and cross-org access. See Creating
additional clients with getOctokit for details and
examples.
Orchestration ID in user-agent — The
ACTIONS_ORCHESTRATION_ID environment variable is
automatically appended to the user-agent string for request
tracing.
Breaking changes:
require('@actions/github') no longer works in
scripts. The upgrade to @actions/github v9
(ESM-only) means require('@actions/github') will fail at
runtime. If you previously used patterns like const { getOctokit }
= require('@actions/github') to create secondary clients, use the
new injected getOctokit function instead — it's available
directly in the script context with no imports needed.
getOctokit is now an injected function parameter.
Scripts that declare const getOctokit = ... or let
getOctokit = ... will get a SyntaxError because
JavaScript does not allow const/let
redeclaration of function parameters. Use the injected
getOctokit directly, or use var getOctokit =
... if you need to redeclare it.
If your script accesses other @actions/github internals
beyond the standard github/octokit client, you
may need to update those references for v9 compatibility.
What's Changed
New Contributors
Full Changelog : https://github.com/actions/github-script/compare/v8.0.0...v9.0.0
v8.0.0
What's Changed
⚠️ Minimum Compatible Runner Version
v2.327.1
Release
Notes
Make sure your runner is updated to this version or newer to use this
release.
New Contributors
Full Changelog : https://github.com/actions/github-script/compare/v7.1.0...v8.0.0
Commits
3a2844b
Merge pull request #700
from actions/salmanmkc/expose-getoctokit + prepare re...
ca10bbd
fix: use @octokit/core/types import for v7
compatibility
86e48e2
merge: incorporate main branch changes
c108472
chore: rebuild dist for v9 upgrade and getOctokit factory
afff112
Merge pull request #712
from actions/salmanmkc/deployment-false + fix user-ag...
ff8117e
ci: fix user-agent test to handle orchestration ID
81c6b78
ci: use deployment: false to suppress deployment noise from integration
tests
3953caf
docs: update README examples from @v8 to @v9 , add getOctokit docs
and v9 brea...
c17d55b
ci: add getOctokit integration test job
a047196
test: add getOctokit integration tests via callAsyncFunction
Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/braintrust-preview-scorers-cleanup.yml | 2 +-
.github/workflows/braintrust-preview-scorers-deploy.yml | 2 +-
.github/workflows/docs-lint-v2-comment.yml | 4 ++--
.github/workflows/external-pr-comment.yml | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/braintrust-preview-scorers-cleanup.yml b/.github/workflows/braintrust-preview-scorers-cleanup.yml
index 0308dd685e508..f7c28d18e86e0 100644
--- a/.github/workflows/braintrust-preview-scorers-cleanup.yml
+++ b/.github/workflows/braintrust-preview-scorers-cleanup.yml
@@ -43,7 +43,7 @@ jobs:
done
- name: Post cleanup comment
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const prNumber = context.payload.pull_request.number
diff --git a/.github/workflows/braintrust-preview-scorers-deploy.yml b/.github/workflows/braintrust-preview-scorers-deploy.yml
index a0127505bdbcc..94c6ab8517938 100644
--- a/.github/workflows/braintrust-preview-scorers-deploy.yml
+++ b/.github/workflows/braintrust-preview-scorers-deploy.yml
@@ -77,7 +77,7 @@ jobs:
- name: Post PR comment
if: steps.changed.outputs.skip != 'true'
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
BRAINTRUST_STAGING_PROJECT_ID: ${{ secrets.BRAINTRUST_STAGING_PROJECT_ID }}
SCORER_SLUGS: ${{ steps.push.outputs.slugs }}
diff --git a/.github/workflows/docs-lint-v2-comment.yml b/.github/workflows/docs-lint-v2-comment.yml
index 7c4561d36950b..56b6d0ed183ac 100644
--- a/.github/workflows/docs-lint-v2-comment.yml
+++ b/.github/workflows/docs-lint-v2-comment.yml
@@ -25,7 +25,7 @@ jobs:
steps:
- id: download_artifact
name: 'Download artifact'
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
@@ -53,7 +53,7 @@ jobs:
run: unzip lint_results.zip
- name: 'Comment on PR'
if: steps.download_artifact.outputs.contains_results == 'true'
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const fs = require('fs');
diff --git a/.github/workflows/external-pr-comment.yml b/.github/workflows/external-pr-comment.yml
index d330ad18b97c9..ea0819f76c603 100644
--- a/.github/workflows/external-pr-comment.yml
+++ b/.github/workflows/external-pr-comment.yml
@@ -13,7 +13,7 @@ jobs:
if: github.event.pull_request.head.repo.full_name != github.repository
steps:
- name: Comment on PR
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const commentBody = `
From 52a25c2ebb1b7151609df9accc7709871c3513fb Mon Sep 17 00:00:00 2001
From: Charis <26616127+charislam@users.noreply.github.com>
Date: Tue, 14 Jul 2026 15:14:12 -0400
Subject: [PATCH 5/7] refactor(sql-editor): extract AI/diff + shortcuts hooks
(decompose 5/6) (#47935)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## What
Decompose step **5 of 6** for `SQLEditor.tsx`. Extracts the Assistant /
diff cluster and the keyboard-shortcut wiring out of the
`SQLEditorContent` monolith into two focused, individually-testable
hooks:
- **`useSqlEditorAi`** — SQL completion (`complete`), the ask-AI prompt
flow (`handlePrompt`), accept/discard diff handlers, `onDebug` /
`buildDebugPrompt` helpers, `handleDiffEditorMount`, and the fragile
diff lifecycle effects (one-shot diff-request drain, diff-editor value
sync, ask-AI widget visibility).
- **`useSqlEditorShortcuts`** — the registered shortcuts (focus editor,
new snippet, format, explain) plus the accept/discard/escape keydown
handling.
`SQLEditorContent` now composes these hooks alongside the
execution/explain hooks landed in decompose 4.
## Behavior-preserving
This is a pure extraction. The moved function bodies, effect logic,
dependency arrays, and JSX are unchanged from the previous monolith
(verified via `git diff` against the pre-decomposition source). In
particular:
- `useEffectEvent` is preserved for `drainDiffRequest` / `resetDiff`.
- `editorMountCount` remains single-owner (passed into the AI hook to
drive the one-shot drain).
- The untrusted→safe SQL promotion (`acceptUntrustedSql`) continues to
happen in the run/explain gesture and warning-modal handlers in
`SQLEditorContent`, as close to the explicit user action as possible.
The Phase-1 characterization suite (`SQLEditor.test.tsx`, 11 tests)
remains green.
## Stack
Part of the SQLEditor decomposition stack (1/6 … 6/6). Builds on
decompose 4 (execution + explain hooks, #47923). Next: PR6 splits the
JSX into panes + final cleanup.
## Summary by CodeRabbit
* **New Features**
* Improved SQL editor AI assistance, including completion prompts,
debugging support, and diff review controls.
* Added keyboard shortcuts for accepting or discarding AI-generated SQL
changes.
* Added shortcuts for focusing the editor, creating snippets, formatting
queries, and explaining SQL.
* **Bug Fixes**
* Prevented SQL execution while reviewing AI-generated differences.
* Improved handling of AI diff state during editor loading and
interaction.
---
.../interfaces/SQLEditor/SQLEditor.tsx | 411 ++----------------
.../interfaces/SQLEditor/useSqlEditorAi.ts | 373 ++++++++++++++++
.../SQLEditor/useSqlEditorShortcuts.ts | 87 ++++
.../components/SQLEditor/SQLEditor.test.tsx | 5 +-
4 files changed, 497 insertions(+), 379 deletions(-)
create mode 100644 apps/studio/components/interfaces/SQLEditor/useSqlEditorAi.ts
create mode 100644 apps/studio/components/interfaces/SQLEditor/useSqlEditorShortcuts.ts
diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx
index 20156f9480a78..aa10baf4cf820 100644
--- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx
+++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx
@@ -2,50 +2,30 @@ import { acceptUntrustedSql, untrustedSql, type UntrustedSqlFragment } from '@su
import { LOCAL_STORAGE_KEYS, useFlag, useParams } from 'common'
import { Loader2 } from 'lucide-react'
import dynamic from 'next/dynamic'
-import { useRouter } from 'next/router'
import { useCallback, useEffect, useEffectEvent, useState } from 'react'
-import { toast } from 'sonner'
import { cn, ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'ui'
import { useSqlEditorDiff, useSqlEditorPrompt } from './hooks'
import { RunQueryWarningModal } from './RunQueryWarningModal'
-import { sqlAiDisclaimerComment } from './SQLEditor.constants'
-import { DiffType } from './SQLEditor.types'
-import {
- appendEnableRLSStatements,
- assembleCompletionDiff,
- buildDebugPromptText,
- createSqlSnippetSkeletonV2,
-} from './SQLEditor.utils'
+import { appendEnableRLSStatements } from './SQLEditor.utils'
import { SQLEditorProvider, useSQLEditorContext } from './SQLEditorContext'
import { useAddDefinitions } from './useAddDefinitions'
import { useEditorMount } from './useEditorMount'
import { usePrettifyQuery } from './usePrettifyQuery'
import { useSnippetIdentity } from './useSnippetIdentity'
import { useSnippetTitleGenerator } from './useSnippetTitleGenerator'
+import { useSqlEditorAi } from './useSqlEditorAi'
import { useSqlEditorExecution } from './useSqlEditorExecution'
import { useSqlEditorExplain } from './useSqlEditorExplain'
+import { useSqlEditorShortcuts } from './useSqlEditorShortcuts'
import { UtilityActions } from './UtilityPanel/UtilityActions'
import { UtilityPanel } from './UtilityPanel/UtilityPanel'
-import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import ResizableAIWidget from '@/components/ui/AIEditor/ResizableAIWidget'
-import { constructHeaders, isValidConnString } from '@/data/fetchers'
+import { isValidConnString } from '@/data/fetchers'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
-import { isError } from '@/data/utils/error-check'
-import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
-import { BASE_PATH } from '@/lib/constants'
-import { formatSql } from '@/lib/formatSql'
import { detectOS } from '@/lib/helpers'
-import { useProfile } from '@/lib/profile'
-import { useTrack } from '@/lib/telemetry/track'
-import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
-import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
-import { useShortcut } from '@/state/shortcuts/useShortcut'
-import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
-import { useSqlEditorDiffRequestSnapshot } from '@/state/sql-editor/sql-editor-diff-request'
-import { useSqlEditorSessionSnapshot } from '@/state/sql-editor/sql-editor-session-state'
import {
getSqlEditorV2StateSnapshot,
useSqlEditorV2StateSnapshot,
@@ -75,59 +55,25 @@ const SQLEditorContent = () => {
} = useSQLEditorContext()
const os = detectOS()
- const router = useRouter()
const { ref } = useParams()
- const { profile } = useProfile()
const { data: project } = useSelectedProjectQuery()
- const { data: org } = useSelectedOrganizationQuery()
const tabs = useTabsStateSnapshot()
- const aiSnap = useAiAssistantStateSnapshot()
- const { openSidebar } = useSidebarManagerSnapshot()
const snapV2 = useSqlEditorV2StateSnapshot()
- const sessionSnap = useSqlEditorSessionSnapshot()
- const diffRequest = useSqlEditorDiffRequestSnapshot()
const databaseSelectorState = useDatabaseSelectorStateSnapshot()
// [Ali] Kill switch to hide the SQL Editor Explain tab and its entry points
const disablePrettyExplain = useFlag('DisablePrettyExplainOnSqlEditor')
- const {
- sourceSqlDiff,
- setSourceSqlDiff,
- selectedDiffType,
- setSelectedDiffType,
- setIsAcceptDiffLoading,
- isDiffOpen,
- defaultSqlDiff,
- closeDiff,
- } = useSqlEditorDiff()
- const { promptState, setPromptState, promptInput, setPromptInput, resetPrompt } =
- useSqlEditorPrompt()
+ const diff = useSqlEditorDiff()
+ const { isDiffOpen, defaultSqlDiff } = diff
+ const prompt = useSqlEditorPrompt()
+ const { promptState, setPromptState, promptInput, setPromptInput, resetPrompt } = prompt
const [hasSelection, setHasSelection] = useState(false)
- const [isDiffEditorMounted, setIsDiffEditorMounted] = useState(false)
-
- const [showWidget, setShowWidget] = useState(false)
const [activeUtilityTab, setActiveUtilityTab] = useState('results')
- useShortcut(SHORTCUT_IDS.SQL_EDITOR_FOCUS_EDITOR, refocusEditor, {
- registerInCommandMenu: true,
- })
-
- const openNewSnippet = useCallback(() => {
- if (!ref) return
- // skip=true bypasses the "load last visited snippet" redirect on /sql/new.
- // Without it, the effect in pages/project/[ref]/sql/[id].tsx bounces back
- // to the previous snippet.
- router.push(`/project/${ref}/sql/new?skip=true`)
- }, [ref, router])
-
- useShortcut(SHORTCUT_IDS.SQL_EDITOR_NEW_SNIPPET, openNewSnippet, {
- registerInCommandMenu: true,
- })
-
const { id, urlId, generatedNewSnippetName, isLoading } = useSnippetIdentity()
const { onMount, editorMountCount } = useEditorMount({ id })
@@ -140,19 +86,14 @@ const SQLEditorContent = () => {
{ enabled: isValidConnString(project?.connectionString) }
)
- const { generateSqlTitle, setAiTitle } = useSnippetTitleGenerator()
- const track = useTrack()
+ const { setAiTitle } = useSnippetTitleGenerator()
const prettifyQuery = usePrettifyQuery({ id, isDiffOpen })
- useShortcut(SHORTCUT_IDS.SQL_EDITOR_FORMAT, prettifyQuery, {
- registerInCommandMenu: true,
- })
-
// Reads the SQL to run from the editor as an UntrustedSqlFragment. The
- // untrusted→safe promotion (acceptUntrustedSql) happens in the small run /
- // explain gesture handlers below — never inside the longer execute* helpers,
- // which by construction only accept already-reviewed SafeSqlFragments.
+ // untrusted→safe promotion (acceptUntrustedSql) happens in the run / explain
+ // gesture + warning-modal handlers below — as close to the user action as
+ // possible — before the SQL reaches the execute* pipelines.
const readEditorSql = useCallback((): UntrustedSqlFragment | undefined => {
const snippet = getSqlEditorV2StateSnapshot().snippets[id]
return getEditorSqlFromEditor(snippet?.snippet.content?.unchecked_sql)
@@ -195,218 +136,30 @@ const SQLEditorContent = () => {
if (sql !== undefined) void executeExplainQuery(acceptUntrustedSql(sql))
}, [executeExplainQuery, readEditorSql])
- useShortcut(SHORTCUT_IDS.SQL_EDITOR_EXPLAIN, handleRunExplain, {
- enabled: !disablePrettyExplain,
- registerInCommandMenu: true,
+ const {
+ handlePrompt,
+ acceptAiHandler,
+ discardAiHandler,
+ onDebug,
+ buildDebugPrompt,
+ handleDiffEditorMount,
+ isCompletionLoading,
+ showWidget,
+ } = useSqlEditorAi({ id, editorMountCount, diff, prompt })
+
+ useSqlEditorShortcuts({
+ isDiffOpen,
+ isPromptOpen: promptState.isOpen,
+ disablePrettyExplain,
+ prettifyQuery,
+ runExplain: handleRunExplain,
+ acceptAiHandler,
+ discardAiHandler,
+ resetPrompt,
})
- const handleNewQuery = useCallback(
- async (sql: string, name: string) => {
- if (!ref) return console.error('Project ref is required')
- if (!profile) return console.error('Profile is required')
- if (!project) return console.error('Project is required')
-
- try {
- const snippet = createSqlSnippetSkeletonV2({
- name,
- sql,
- owner_id: profile.id,
- project_id: project.id,
- })
- snapV2.addSnippet({ projectRef: ref, snippet })
- snapV2.addNeedsSaving(snippet.id!)
- router.push(`/project/${ref}/sql/${snippet.id}`)
- } catch (error: any) {
- toast.error(`Failed to create new query: ${error.message}`)
- }
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [profile?.id, project?.id, ref, router, snapV2]
- )
-
- const buildDebugPrompt = useCallback(() => {
- const snippet = snapV2.snippets[id]
- const result = sessionSnap.results[id]?.[0]
- const sql = (snippet?.snippet.content?.unchecked_sql ?? '')
- .replace(sqlAiDisclaimerComment, '')
- .trim()
- const errorMessage = result?.error?.message ?? 'Unknown error'
-
- return buildDebugPromptText(sql, errorMessage)
- }, [id, sessionSnap.results, snapV2.snippets])
-
- const onDebug = useCallback(async () => {
- try {
- const snippet = snapV2.snippets[id]
- const result = sessionSnap.results[id]?.[0]
- openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
- aiSnap.newChat({
- name: 'Debug SQL snippet',
- sqlSnippets: [
- (snippet.snippet.content?.unchecked_sql ?? '').replace(sqlAiDisclaimerComment, '').trim(),
- ],
- initialInput: `Help me to debug the attached sql snippet which gives the following error: \n\n${result.error.message}`,
- })
- } catch (error: unknown) {
- // [Joshen] There's a tendency for the SQL debug to chuck a lengthy error message
- // that's not relevant for the user - so we prettify it here by avoiding to return the
- // entire error body from the assistant
- if (isError(error)) {
- toast.error(
- `Sorry, the assistant failed to debug your query! Please try again with a different one.`
- )
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [id, sessionSnap.results, snapV2.snippets])
-
- const acceptAiHandler = useCallback(async () => {
- try {
- setIsAcceptDiffLoading(true)
-
- // TODO: show error if undefined
- if (!sourceSqlDiff || !editorRef.current || !diffEditorRef.current) return
-
- const editorModel = editorRef.current.getModel()
- const diffModel = diffEditorRef.current.getModel()
-
- if (!editorModel || !diffModel) return
-
- const sql = diffModel.modified.getValue()
-
- if (selectedDiffType === DiffType.NewSnippet) {
- const { title } = await generateSqlTitle({ sql })
- await handleNewQuery(sql, title)
- } else {
- editorRef.current.executeEdits('apply-ai-edit', [
- {
- text: sql,
- range: editorModel.getFullModelRange(),
- },
- ])
- }
-
- track('assistant_sql_diff_handler_evaluated', { handlerAccepted: true })
-
- setSelectedDiffType(DiffType.Modification)
- resetPrompt()
- closeDiff()
- } finally {
- setIsAcceptDiffLoading(false)
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [sourceSqlDiff, selectedDiffType, handleNewQuery, generateSqlTitle, router, id, snapV2, track])
-
- const discardAiHandler = useCallback(() => {
- track('assistant_sql_diff_handler_evaluated', { handlerAccepted: false })
- resetPrompt()
- closeDiff()
- }, [closeDiff, resetPrompt, track])
-
- const [isCompletionLoading, setIsCompletionLoading] = useState(false)
-
- const complete = useCallback(
- async (
- _prompt: string,
- options?: {
- headers?: Record
- body?: { completionMetadata?: any }
- }
- ) => {
- try {
- setIsCompletionLoading(true)
-
- const response = await fetch(`${BASE_PATH}/api/ai/code/complete`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- ...(options?.headers ?? {}),
- },
- body: JSON.stringify({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- language: 'sql',
- orgSlug: org?.slug,
- ...(options?.body ?? {}),
- }),
- })
-
- if (!response.ok) {
- const errorText = await response.text()
- throw new Error(errorText || 'Failed to generate completion')
- }
-
- // API returns a JSON-encoded string
- const text: string = await response.json()
-
- const meta = options?.body?.completionMetadata ?? {}
- const { original, modified } = assembleCompletionDiff(meta, text)
-
- const formattedModified = formatSql(modified)
- setSourceSqlDiff({ original, modified: formattedModified })
- setSelectedDiffType(DiffType.Modification)
- setPromptState((prev) => ({ ...prev, isLoading: false }))
- setIsCompletionLoading(false)
- } catch (error: any) {
- toast.error(`Failed to generate SQL: ${error?.message ?? 'Unknown error'}`)
- setIsCompletionLoading(false)
- throw error
- }
- },
- [
- org?.slug,
- project?.connectionString,
- project?.ref,
- setPromptState,
- setSelectedDiffType,
- setSourceSqlDiff,
- ]
- )
-
- const handlePrompt = async (
- prompt: string,
- context: {
- beforeSelection: string
- selection: string
- afterSelection: string
- }
- ) => {
- try {
- setPromptState((prev) => ({
- ...prev,
- selection: context.selection,
- beforeSelection: context.beforeSelection,
- afterSelection: context.afterSelection,
- }))
- const headerData = await constructHeaders()
-
- const authorizationHeader = headerData.get('Authorization')
-
- await complete(prompt, {
- ...(authorizationHeader ? { headers: { Authorization: authorizationHeader } } : undefined),
- body: {
- completionMetadata: {
- textBeforeCursor: context.beforeSelection,
- textAfterCursor: context.afterSelection,
- language: 'pgsql',
- prompt,
- selection: context.selection,
- },
- },
- })
- } catch (error) {
- setPromptState((prev) => ({ ...prev, isLoading: false }))
- }
- }
-
/** All useEffects are at the bottom before returning the TSX */
- const resetDiff = useEffectEvent(() => {
- if (id) {
- closeDiff()
- setPromptState((prev) => ({ ...prev, isOpen: false }))
- }
- })
const saveScrollPosition = useEffectEvent((snippetId: string) => {
if (ref) {
const tabId = createTabId('sql', { id: snippetId })
@@ -414,58 +167,12 @@ const SQLEditorContent = () => {
}
})
useEffect(() => {
- resetDiff()
+ // Save the departing snippet's scroll position on unmount / snippet switch.
return () => saveScrollPosition(id)
// Temporary until we update eslint to ignore useEffectEvent
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id])
- useEffect(() => {
- const handler = (e: KeyboardEvent) => {
- if (!isDiffOpen && !promptState.isOpen) return
-
- switch (e.key) {
- case 'Enter':
- if ((os === 'macos' ? e.metaKey : e.ctrlKey) && isDiffOpen) {
- acceptAiHandler()
- resetPrompt()
- }
- return
- case 'Escape':
- if (isDiffOpen) discardAiHandler()
- resetPrompt()
- editorRef.current?.focus()
- return
- }
- }
- window.addEventListener('keydown', handler)
- return () => window.removeEventListener('keydown', handler)
- }, [
- editorRef,
- os,
- isDiffOpen,
- promptState.isOpen,
- acceptAiHandler,
- discardAiHandler,
- resetPrompt,
- ])
-
- useEffect(() => {
- if (isDiffOpen) {
- const diffEditor = diffEditorRef.current
- const model = diffEditor?.getModel()
- if (model && model.original && model.modified) {
- model.original.setValue(defaultSqlDiff.original)
- model.modified.setValue(defaultSqlDiff.modified)
- // scroll to the start line of the modification
- const modifiedEditor = diffEditor!.getModifiedEditor()
- const startLine = promptState.startLineNumber
- modifiedEditor.revealLineInCenter(startLine)
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [selectedDiffType, sourceSqlDiff])
-
useEffect(() => {
if (isSuccessReadReplicas) {
const primaryDatabase = databases.find((db) => db.identifier === ref)
@@ -474,53 +181,6 @@ const SQLEditorContent = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSuccessReadReplicas, databases, ref])
- const drainDiffRequest = useEffectEvent(() => {
- const request = diffRequest.pending
- if (request === undefined) return
-
- const editorModel = editorRef.current?.getModel()
- // Editor isn't ready yet; leave the request pending. editorMountCount bumps
- // on mount and re-runs this effect, so the request applies once mounted.
- if (!editorModel) return
-
- const { diffType, sql } = request
- const existingValue = editorRef.current?.getValue() ?? ''
- if (existingValue.length === 0) {
- // if the editor is empty, just copy over the code
- editorRef.current?.executeEdits('apply-ai-message', [
- {
- text: `${sql}`,
- range: editorModel.getFullModelRange(),
- },
- ])
- } else {
- const currentSql = editorRef.current?.getValue()
- const diff = { original: currentSql || '', modified: sql }
- setSourceSqlDiff(diff)
- setSelectedDiffType(diffType)
- }
-
- // One-shot: drain the request so it can't re-apply to a later editor or session.
- diffRequest.consumeDiffRequest()
- })
- useEffect(() => {
- drainDiffRequest()
- // until we can upgrade eslint to ignore useEffectEvent
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [diffRequest.pending, editorMountCount])
-
- // We want to check if the diff editor is mounted and if it is, we want to show the widget
- // We also want to cleanup the widget when the diff editor is closed
- useEffect(() => {
- if (!isDiffOpen) {
- setIsDiffEditorMounted(false)
- setShowWidget(false)
- } else if (diffEditorRef.current && isDiffEditorMounted) {
- setShowWidget(true)
- return () => setShowWidget(false)
- }
- }, [diffEditorRef, isDiffOpen, isDiffEditorMounted])
-
return (
<>
{
language="pgsql"
original={defaultSqlDiff.original}
modified={defaultSqlDiff.modified}
- onMount={(editor) => {
- diffEditorRef.current = editor
- setIsDiffEditorMounted(true)
- }}
+ onMount={handleDiffEditorMount}
/>
{showWidget && (
+ prompt: ReturnType
+}
+
+/**
+ * Owns the Assistant / diff cluster: SQL completion, the ask-AI prompt flow, the
+ * accept/discard diff handlers, the debug-prompt helpers, and the fragile diff
+ * lifecycle effects (one-shot diff-request drain, diff-editor value sync, and the
+ * ask-AI widget visibility).
+ */
+export function useSqlEditorAi({ id, editorMountCount, diff, prompt }: UseSqlEditorAiArgs) {
+ const {
+ sourceSqlDiff,
+ setSourceSqlDiff,
+ selectedDiffType,
+ setSelectedDiffType,
+ setIsAcceptDiffLoading,
+ isDiffOpen,
+ defaultSqlDiff,
+ closeDiff,
+ } = diff
+ const { promptState, setPromptState, resetPrompt } = prompt
+
+ const { editorRef, diffEditorRef } = useSQLEditorContext()
+
+ const router = useRouter()
+ const { ref } = useParams()
+ const { profile } = useProfile()
+ const { data: project } = useSelectedProjectQuery()
+ const { data: org } = useSelectedOrganizationQuery()
+ const track = useTrack()
+ const snapV2 = useSqlEditorV2StateSnapshot()
+ const sessionSnap = useSqlEditorSessionSnapshot()
+ const aiSnap = useAiAssistantStateSnapshot()
+ const { openSidebar } = useSidebarManagerSnapshot()
+ const diffRequest = useSqlEditorDiffRequestSnapshot()
+ const { generateSqlTitle } = useSnippetTitleGenerator()
+
+ const [isCompletionLoading, setIsCompletionLoading] = useState(false)
+ const [isDiffEditorMounted, setIsDiffEditorMounted] = useState(false)
+ const [showWidget, setShowWidget] = useState(false)
+
+ const handleNewQuery = useCallback(
+ async (sql: string, name: string) => {
+ if (!ref) return console.error('Project ref is required')
+ if (!profile) return console.error('Profile is required')
+ if (!project) return console.error('Project is required')
+
+ try {
+ const snippet = createSqlSnippetSkeletonV2({
+ name,
+ sql,
+ owner_id: profile.id,
+ project_id: project.id,
+ })
+ snapV2.addSnippet({ projectRef: ref, snippet })
+ snapV2.addNeedsSaving(snippet.id!)
+ router.push(`/project/${ref}/sql/${snippet.id}`)
+ } catch (error: any) {
+ toast.error(`Failed to create new query: ${error.message}`)
+ }
+ },
+ [profile, project, ref, router, snapV2]
+ )
+
+ const buildDebugPrompt = useCallback(() => {
+ const snippet = snapV2.snippets[id]
+ const result = sessionSnap.results[id]?.[0]
+ const sql = (snippet?.snippet.content?.unchecked_sql ?? '')
+ .replace(sqlAiDisclaimerComment, '')
+ .trim()
+ const errorMessage = result?.error?.message ?? 'Unknown error'
+
+ return buildDebugPromptText(sql, errorMessage)
+ }, [id, sessionSnap.results, snapV2.snippets])
+
+ const onDebug = useCallback(async () => {
+ try {
+ const snippet = snapV2.snippets[id]
+ const result = sessionSnap.results[id]?.[0]
+ openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
+ aiSnap.newChat({
+ name: 'Debug SQL snippet',
+ sqlSnippets: [
+ (snippet.snippet.content?.unchecked_sql ?? '').replace(sqlAiDisclaimerComment, '').trim(),
+ ],
+ initialInput: `Help me to debug the attached sql snippet which gives the following error: \n\n${result.error.message}`,
+ })
+ } catch (error: unknown) {
+ // [Joshen] There's a tendency for the SQL debug to chuck a lengthy error message
+ // that's not relevant for the user - so we prettify it here by avoiding to return the
+ // entire error body from the assistant
+ if (isError(error)) {
+ toast.error(
+ `Sorry, the assistant failed to debug your query! Please try again with a different one.`
+ )
+ }
+ }
+ }, [id, sessionSnap.results, snapV2.snippets, aiSnap, openSidebar])
+
+ const acceptAiHandler = useCallback(async () => {
+ try {
+ setIsAcceptDiffLoading(true)
+
+ // TODO: show error if undefined
+ if (!sourceSqlDiff || !editorRef.current || !diffEditorRef.current) return
+
+ const editorModel = editorRef.current.getModel()
+ const diffModel = diffEditorRef.current.getModel()
+
+ if (!editorModel || !diffModel) return
+
+ const sql = diffModel.modified.getValue()
+
+ if (selectedDiffType === DiffType.NewSnippet) {
+ const { title } = await generateSqlTitle({ sql })
+ await handleNewQuery(sql, title)
+ } else {
+ editorRef.current.executeEdits('apply-ai-edit', [
+ {
+ text: sql,
+ range: editorModel.getFullModelRange(),
+ },
+ ])
+ }
+
+ track('assistant_sql_diff_handler_evaluated', { handlerAccepted: true })
+
+ setSelectedDiffType(DiffType.Modification)
+ resetPrompt()
+ closeDiff()
+ } finally {
+ setIsAcceptDiffLoading(false)
+ }
+ }, [
+ editorRef,
+ diffEditorRef,
+ sourceSqlDiff,
+ selectedDiffType,
+ generateSqlTitle,
+ handleNewQuery,
+ track,
+ setIsAcceptDiffLoading,
+ setSelectedDiffType,
+ resetPrompt,
+ closeDiff,
+ ])
+
+ const discardAiHandler = useCallback(() => {
+ track('assistant_sql_diff_handler_evaluated', { handlerAccepted: false })
+ resetPrompt()
+ closeDiff()
+ }, [closeDiff, resetPrompt, track])
+
+ const complete = useCallback(
+ async (
+ _prompt: string,
+ options?: {
+ headers?: Record
+ body?: { completionMetadata?: any }
+ }
+ ) => {
+ try {
+ setIsCompletionLoading(true)
+
+ const response = await fetch(`${BASE_PATH}/api/ai/code/complete`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(options?.headers ?? {}),
+ },
+ body: JSON.stringify({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ language: 'sql',
+ orgSlug: org?.slug,
+ ...(options?.body ?? {}),
+ }),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ throw new Error(errorText || 'Failed to generate completion')
+ }
+
+ // API returns a JSON-encoded string
+ const text: string = await response.json()
+
+ const meta = options?.body?.completionMetadata ?? {}
+ const { original, modified } = assembleCompletionDiff(meta, text)
+
+ const formattedModified = formatSql(modified)
+ setSourceSqlDiff({ original, modified: formattedModified })
+ setSelectedDiffType(DiffType.Modification)
+ setPromptState((prev) => ({ ...prev, isLoading: false }))
+ setIsCompletionLoading(false)
+ } catch (error: any) {
+ toast.error(`Failed to generate SQL: ${error?.message ?? 'Unknown error'}`)
+ setIsCompletionLoading(false)
+ throw error
+ }
+ },
+ [
+ org?.slug,
+ project?.connectionString,
+ project?.ref,
+ setPromptState,
+ setSelectedDiffType,
+ setSourceSqlDiff,
+ ]
+ )
+
+ const handlePrompt = async (
+ prompt: string,
+ context: {
+ beforeSelection: string
+ selection: string
+ afterSelection: string
+ }
+ ) => {
+ try {
+ setPromptState((prev) => ({
+ ...prev,
+ selection: context.selection,
+ beforeSelection: context.beforeSelection,
+ afterSelection: context.afterSelection,
+ }))
+ const headerData = await constructHeaders()
+
+ const authorizationHeader = headerData.get('Authorization')
+
+ await complete(prompt, {
+ ...(authorizationHeader ? { headers: { Authorization: authorizationHeader } } : undefined),
+ body: {
+ completionMetadata: {
+ textBeforeCursor: context.beforeSelection,
+ textAfterCursor: context.afterSelection,
+ language: 'pgsql',
+ prompt,
+ selection: context.selection,
+ },
+ },
+ })
+ } catch (error) {
+ setPromptState((prev) => ({ ...prev, isLoading: false }))
+ }
+ }
+
+ const handleDiffEditorMount = (editor: IStandaloneDiffEditor) => {
+ diffEditorRef.current = editor
+ setIsDiffEditorMounted(true)
+ }
+
+ const resetDiff = useEffectEvent(() => {
+ if (id) {
+ closeDiff()
+ setPromptState((prev) => ({ ...prev, isOpen: false }))
+ }
+ })
+ useEffect(() => {
+ resetDiff()
+ // Temporary until we update eslint to ignore useEffectEvent
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [id])
+
+ useEffect(() => {
+ if (isDiffOpen) {
+ const diffEditor = diffEditorRef.current
+ const model = diffEditor?.getModel()
+ if (model && model.original && model.modified) {
+ model.original.setValue(defaultSqlDiff.original)
+ model.modified.setValue(defaultSqlDiff.modified)
+ // scroll to the start line of the modification
+ const modifiedEditor = diffEditor!.getModifiedEditor()
+ const startLine = promptState.startLineNumber
+ modifiedEditor.revealLineInCenter(startLine)
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [selectedDiffType, sourceSqlDiff])
+
+ const drainDiffRequest = useEffectEvent(() => {
+ const request = diffRequest.pending
+ if (request === undefined) return
+
+ const editorModel = editorRef.current?.getModel()
+ // Editor isn't ready yet; leave the request pending. editorMountCount bumps
+ // on mount and re-runs this effect, so the request applies once mounted.
+ if (!editorModel) return
+
+ const { diffType, sql } = request
+ const existingValue = editorRef.current?.getValue() ?? ''
+ if (existingValue.length === 0) {
+ // if the editor is empty, just copy over the code
+ editorRef.current?.executeEdits('apply-ai-message', [
+ {
+ text: `${sql}`,
+ range: editorModel.getFullModelRange(),
+ },
+ ])
+ } else {
+ const currentSql = editorRef.current?.getValue()
+ const diff = { original: currentSql || '', modified: sql }
+ setSourceSqlDiff(diff)
+ setSelectedDiffType(diffType)
+ }
+
+ // One-shot: drain the request so it can't re-apply to a later editor or session.
+ diffRequest.consumeDiffRequest()
+ })
+ useEffect(() => {
+ drainDiffRequest()
+ // until we can upgrade eslint to ignore useEffectEvent
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [diffRequest.pending, editorMountCount])
+
+ // We want to check if the diff editor is mounted and if it is, we want to show the widget
+ // We also want to cleanup the widget when the diff editor is closed
+ useEffect(() => {
+ if (!isDiffOpen) {
+ setIsDiffEditorMounted(false)
+ setShowWidget(false)
+ } else if (diffEditorRef.current && isDiffEditorMounted) {
+ setShowWidget(true)
+ return () => setShowWidget(false)
+ }
+ }, [diffEditorRef, isDiffOpen, isDiffEditorMounted])
+
+ return {
+ handlePrompt,
+ acceptAiHandler,
+ discardAiHandler,
+ onDebug,
+ buildDebugPrompt,
+ handleDiffEditorMount,
+ isCompletionLoading,
+ showWidget,
+ }
+}
diff --git a/apps/studio/components/interfaces/SQLEditor/useSqlEditorShortcuts.ts b/apps/studio/components/interfaces/SQLEditor/useSqlEditorShortcuts.ts
new file mode 100644
index 0000000000000..4f119980d36ef
--- /dev/null
+++ b/apps/studio/components/interfaces/SQLEditor/useSqlEditorShortcuts.ts
@@ -0,0 +1,87 @@
+import { useParams } from 'common'
+import { useRouter } from 'next/router'
+import { useCallback, useEffect } from 'react'
+
+import { useSQLEditorContext } from './SQLEditorContext'
+import { detectOS } from '@/lib/helpers'
+import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
+import { useShortcut } from '@/state/shortcuts/useShortcut'
+
+type UseSqlEditorShortcutsArgs = {
+ isDiffOpen: boolean
+ isPromptOpen: boolean
+ disablePrettyExplain: boolean
+ prettifyQuery: () => void
+ runExplain: () => void
+ acceptAiHandler: () => void
+ discardAiHandler: () => void
+ resetPrompt: () => void
+}
+
+/**
+ * Registers the SQL editor's keyboard shortcuts (focus editor, new snippet,
+ * format, explain) plus the window keydown that accepts/discards an open AI diff
+ * or dismisses the prompt.
+ */
+export function useSqlEditorShortcuts({
+ isDiffOpen,
+ isPromptOpen,
+ disablePrettyExplain,
+ prettifyQuery,
+ runExplain,
+ acceptAiHandler,
+ discardAiHandler,
+ resetPrompt,
+}: UseSqlEditorShortcutsArgs) {
+ const os = detectOS()
+ const router = useRouter()
+ const { ref } = useParams()
+ const { editorRef, refocusEditor } = useSQLEditorContext()
+
+ const openNewSnippet = useCallback(() => {
+ if (!ref) return
+ // skip=true bypasses the "load last visited snippet" redirect on /sql/new.
+ // Without it, the effect in pages/project/[ref]/sql/[id].tsx bounces back
+ // to the previous snippet.
+ router.push(`/project/${ref}/sql/new?skip=true`)
+ }, [ref, router])
+
+ useShortcut(SHORTCUT_IDS.SQL_EDITOR_FOCUS_EDITOR, refocusEditor, {
+ registerInCommandMenu: true,
+ })
+
+ useShortcut(SHORTCUT_IDS.SQL_EDITOR_NEW_SNIPPET, openNewSnippet, {
+ registerInCommandMenu: true,
+ })
+
+ useShortcut(SHORTCUT_IDS.SQL_EDITOR_FORMAT, prettifyQuery, {
+ registerInCommandMenu: true,
+ })
+
+ useShortcut(SHORTCUT_IDS.SQL_EDITOR_EXPLAIN, runExplain, {
+ enabled: !disablePrettyExplain,
+ registerInCommandMenu: true,
+ })
+
+ useEffect(() => {
+ const handler = (e: KeyboardEvent) => {
+ if (!isDiffOpen && !isPromptOpen) return
+
+ switch (e.key) {
+ case 'Enter':
+ if ((os === 'macos' ? e.metaKey : e.ctrlKey) && isDiffOpen) {
+ acceptAiHandler()
+ resetPrompt()
+ }
+ return
+ case 'Escape':
+ if (isDiffOpen) discardAiHandler()
+ resetPrompt()
+ editorRef.current?.focus()
+ return
+ }
+ }
+ window.addEventListener('keydown', handler)
+ return () => window.removeEventListener('keydown', handler)
+ }, [editorRef, os, isDiffOpen, isPromptOpen, acceptAiHandler, discardAiHandler, resetPrompt])
+}
diff --git a/apps/studio/tests/components/SQLEditor/SQLEditor.test.tsx b/apps/studio/tests/components/SQLEditor/SQLEditor.test.tsx
index 8a90205a360be..bb7b7ae1bac78 100644
--- a/apps/studio/tests/components/SQLEditor/SQLEditor.test.tsx
+++ b/apps/studio/tests/components/SQLEditor/SQLEditor.test.tsx
@@ -429,11 +429,12 @@ describe('SQLEditor characterization', () => {
sqlEditorDiffRequestState.requestDiff('select 42;', DiffType.Modification)
await renderEditor()
- await screen.findByTestId('diff-editor')
+ // The queued request drains on mount and opens a diff, which disables Run.
+ // (Assert via the run button rather than the dynamically-imported diff editor.)
+ await waitFor(() => expect(screen.getByTestId('run-button')).toBeDisabled())
const addResult = vi.spyOn(sqlEditorSessionState, 'addResult')
// The run button is disabled while diffing; clicking should not execute.
- expect(screen.getByTestId('run-button')).toBeDisabled()
fireEvent.click(screen.getByTestId('run-button'))
// Give any async work a chance to (not) happen.
await new Promise((r) => setTimeout(r, 20))
From c8d60097d9f36d9990c0a130434e15c280a16f77 Mon Sep 17 00:00:00 2001
From: Chris Stockton <180184+cstockton@users.noreply.github.com>
Date: Tue, 14 Jul 2026 12:27:26 -0700
Subject: [PATCH 6/7] fix(docs): before-user-created hook had invalid ipnet cmp
(#47940)
The comparison operator `<<` check if lhs is strictly contained by the
rhs. Meaning a `/32` would return false for `/32` as the lhs is not
contained, but equal to the rhs. The `<<=` is operator checks if a
subnet is strictly contained or equal to which fixes the `/32` case.
[1] https://www.postgresql.org/docs/17/functions-net.html#FUNCTIONS-NET
## Summary by CodeRabbit
* **Documentation**
* Updated the `before-user-created` hook SQL example to use the correct
IP/CIDR containment syntax for both allow and deny network checks.
Co-authored-by: Chris Stockton
---
.../guides/auth/auth-hooks/before-user-created-hook.mdx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx b/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx
index c76f9deadf8eb..5d9b56bcbef06 100644
--- a/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx
+++ b/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx
@@ -404,7 +404,7 @@ begin
-- Step 1: Check for explicit allow
select count(*) into allow_count
from public.signup_networks
- where type = 'allow' and ip::inet << cidr;
+ where type = 'allow' and ip <<= cidr;
if allow_count > 0 then
-- If explicitly allowed, allow signup
@@ -414,7 +414,7 @@ begin
-- Step 2: Check for explicit deny
select count(*) into deny_count
from public.signup_networks
- where type = 'deny' and ip::inet << cidr;
+ where type = 'deny' and ip <<= cidr;
if deny_count > 0 then
return jsonb_build_object(
From 320c2501064bbc8b99b1aeb1f4adde5fc7f89112 Mon Sep 17 00:00:00 2001
From: Stephanie Jackson
Date: Tue, 14 Jul 2026 16:50:24 -0400
Subject: [PATCH 7/7] Add Stephanie Jackson to humans.txt (#47937)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## 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?
Docs update
## What is the current behavior?
I am not listed
## What is the new behavior?
I am now listed!
## Additional context
Onboarding task of course.
## Summary by CodeRabbit
* **Documentation**
* Added Stephanie Jackson to the team credits in the project’s public
documentation.
---
apps/docs/public/humans.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt
index 6c1c610fc0b05..2399351a9598a 100644
--- a/apps/docs/public/humans.txt
+++ b/apps/docs/public/humans.txt
@@ -268,6 +268,7 @@ Shane Adams
Shane E
Shreekar Shetty
Sreyas Udayavarman
+Stephanie Jackson (stejacks)
Stephen Morgan
Steve Chavez
Steven Eubank