Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions .github/workflows/docs-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ concurrency:

permissions:
contents: read
deployments: read
statuses: read
pull-requests: read

Expand All @@ -45,8 +44,20 @@ jobs:
persist-credentials: false
sparse-checkout: |
e2e/docs
scripts
patches

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
name: Install pnpm
with:
run_install: false

- name: Use Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: '.nvmrc'
cache: 'pnpm'

# Vercel skips the docs preview when a PR only changes the harness
# (e2e/docs, workflow). Wait for a preview only when apps/docs changed.
- name: Detect docs app changes
Expand All @@ -58,14 +69,21 @@ jobs:
docs_app:
- 'apps/docs/**'

# Vercel's GitHub App stopped writing GitHub Deployment objects on
# 2026-02-17 (broken app auth), so vercel/wait-for-deployment-action
# times out polling that API even though the preview builds fine.
# Poll the "Vercel – docs" commit status instead — Vercel keeps posting
# those — then resolve the deployment it points to via Vercel's own API
# to get the actual preview URL. See scripts/waitForVercelDocsPreview.js.
- name: Wait for Vercel docs preview
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.filter.outputs.docs_app == 'true'
id: deployment
uses: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037
with:
project-slug: docs
environment: preview
timeout: '900'
run: node scripts/waitForVercelDocsPreview.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }}

- name: Resolve base URL
id: base-url
Expand All @@ -87,17 +105,6 @@ jobs:
echo "use_bypass=false" >> "$GITHUB_OUTPUT"
fi

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
name: Install pnpm
with:
run_install: false

- name: Use Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: '.nvmrc'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile --filter=e2e-docs...

Expand Down
46 changes: 32 additions & 14 deletions apps/studio/components/ui/ProjectUpgradeFailedBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { SupportCategories } from '@supabase/shared-types/out/constants'
import { DatabaseUpgradeStatus } from '@supabase/shared-types/out/events'
import { useParams } from 'common'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import dayjs from 'dayjs'
import { X } from 'lucide-react'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns/admonition'

import { ButtonTooltip } from './ButtonTooltip'
import { InlineLink } from './InlineLink'
import { SupportLink } from '@/components/interfaces/Support/SupportLink'
import { useProjectUpgradingStatusQuery } from '@/data/config/project-upgrade-status-query'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useShowPostgresUpgradeLogs } from '@/hooks/misc/useShowPostgresUpgradeLogs'
import { IS_PLATFORM } from '@/lib/constants'
import { guessLocalTimezone } from '@/lib/dayjs'
Expand All @@ -20,7 +23,12 @@ export const ProjectUpgradeFailedBanner = () => {
const { status, initiated_at, latest_status_at, error } = data?.databaseUpgradeStatus ?? {}
const showPostgresUpgradeLogs = useShowPostgresUpgradeLogs()

const isFailed = status === DatabaseUpgradeStatus.Failed
const [dismissedAt, setDismissedAt] = useLocalStorageQuery<string | null>(
LOCAL_STORAGE_KEYS.PROJECT_UPGRADE_FAILED_BANNER_DISMISSED_AT(ref ?? 'unknown'),
null
)

const isFailed = status === DatabaseUpgradeStatus.Failed && initiated_at !== dismissedAt
const initiatedAt = dayjs
.utc(initiated_at ?? 0)
.tz(guessLocalTimezone())
Expand Down Expand Up @@ -48,18 +56,28 @@ export const ProjectUpgradeFailedBanner = () => {
type="warning"
title={`Postgres version upgrade was not successful (Initiated at ${initiatedAt})`}
actions={
<Button asChild variant="default">
<SupportLink
queryParams={{
category: SupportCategories.DATABASE_UNRESPONSIVE,
projectRef: ref,
subject,
message,
}}
>
Contact support
</SupportLink>
</Button>
<>
<Button asChild variant="default">
<SupportLink
queryParams={{
category: SupportCategories.DATABASE_UNRESPONSIVE,
projectRef: ref,
subject,
message,
}}
>
Contact support
</SupportLink>
</Button>
<ButtonTooltip
icon={<X />}
variant="text"
className="w-6"
tooltip={{ content: { side: 'bottom', text: 'Dismiss' } }}
aria-label="Dismiss upgrade failed banner"
onClick={() => setDismissedAt(initiated_at ?? null)}
/>
</>
}
>
<div>
Expand Down
36 changes: 24 additions & 12 deletions apps/www/_events/2026-07-22-supabase-trae-high-quality-apps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ meta_description: >-
and TRAE to see how TRAE SOLO uses your live Supabase context (schema, auth,
RLS policies, and logs) to generate code that holds up in production.
type: webinar
onDemand: false
type_label: 'Supabase Live'
onDemand: true
date: '2026-07-22T19:00:00.000-07:00'
timezone: America/Los_Angeles
duration: 45 mins
Expand All @@ -22,9 +23,9 @@ company:
categories:
- webinar
main_cta:
url: 'https://attendee.gotowebinar.com/register/2865727980603383392'
target: _blank
label: Register now
url: '#recording'
target: _self
label: Watch the recording
speakers: 'gerardo_estaba,gary_qi'
---

Expand All @@ -34,15 +35,26 @@ More people than ever are shipping real products with AI tools. The gap between

TRAE and Supabase are built for both. TRAE gives you the speed of a fully autonomous AI coding agent that takes you from prompt to working app. Supabase handles the backend: open-source managed Postgres with Auth, Storage, Edge Functions, and Realtime built in. When TRAE has live context of your Supabase projects, the code it generates is production-ready from the start.

In this session, Gerardo Estaba from Supabase and Gary Qi from TRAE will show you how to go from prompt to production without compromising on code quality.
In this session, Gerardo Estaba from Supabase and Gary Qi from TRAE show you how to go from prompt to production without compromising on code quality.

### What we'll cover
<div className="video-container mb-8" id="recording">
<iframe
className="w-full"
src="https://www.youtube-nocookie.com/embed/SVxZ_aPq4U4"
title="Building high quality Supabase apps using TRAE"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture; web-share"
allowFullScreen
/>
</div>

- Where AI-generated apps break in production and how TRAE and Supabase catch those problems before they ship
- How TRAE SOLO uses your live Supabase context (schema, auth config, RLS policies, and logs) to generate more accurate code
- Managing your Supabase backend with natural language via the Supabase and TRAE SOLO integration: running migrations, modifying RLS policies, creating storage buckets, and managing secrets
- Live build: a full-stack app from prompt to deployed, with Auth, Storage, and a secure database, built entirely inside TRAE
## Key Takeaways

You'll leave with a clear picture of what the TRAE and Supabase workflow looks like end to end, and what to set up first on your own project.
- Where AI-generated apps break in production, and how TRAE and Supabase catch those problems before they ship

Plus, bring your questions for the live Q&A. Can't make it? Register anyway and we'll send you the recording.
- How TRAE SOLO uses your live Supabase context (schema, Auth config, RLS policies, and logs) to generate more accurate code

- How to manage your Supabase backend in natural language through the Supabase and TRAE SOLO integration: running migrations, modifying RLS policies, creating Storage buckets, and managing secrets

- A full live build of a full-stack app, from prompt to deployed, with Auth, Storage, and a secure database, built entirely inside TRAE

We hope you enjoy the recording!
7 changes: 6 additions & 1 deletion apps/www/lib/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,15 @@ function getAllParsedMdxEvents(): SupabaseEvent[] {
/**
* Read all events under `_events/` and return today-and-future events.
* Past events are excluded (by end_date when present, otherwise start date).
* Events already flipped to `onDemand: true` are excluded too — they've
* already happened and belong in the on-demand bucket (getOnDemandMdxEvents),
* not the upcoming one, regardless of how their UTC-converted date compares to today.
*/
export const getMdxEvents = (): SupabaseEvent[] => {
const today = startOfTodayUtc()
return getAllParsedMdxEvents().filter((event) => new Date(event.end_date ?? event.date) >= today)
return getAllParsedMdxEvents().filter(
(event) => !event.onDemand && new Date(event.end_date ?? event.date) >= today
)
}

/** All MDX events with `onDemand: true`, including past recordings. */
Expand Down
5 changes: 4 additions & 1 deletion apps/www/pages/events/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ interface EventData {
main_cta?: CTA
description: string
type: EventType
type_label?: string
company?: CompanyType
onDemand?: boolean
disable_page_build?: boolean
Expand Down Expand Up @@ -283,7 +284,9 @@ const EventPage = ({ event }: InferGetStaticPropsType<typeof getStaticProps>) =>
<div className="flex flex-col gap-2 md:gap-3 items-start mb-8">
<div className="flex flex-row text-sm items-center flex-wrap">
<Icon className="hidden sm:inline-block w-4 h-4 text-brand mr-2" />
<span className="uppercase text-brand font-mono">{event.type}</span>
<span className="uppercase text-brand font-mono">
{event.type_label ?? event.type}
</span>
<span className="mx-3 px-3 border-x">
{dayjs(event.date).tz(event.timezone).format(`DD MMM YYYY [at] hA z`)}
</span>
Expand Down
2 changes: 2 additions & 0 deletions packages/common/constants/local-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export const LOCAL_STORAGE_KEYS = {
`table-editor-queue-operations-banner-dismissed-${ref}`,
FREE_MICRO_UPGRADE_BANNER_DISMISSED: (ref: string) =>
`free-micro-upgrade-banner-dismissed-${ref}`,
PROJECT_UPGRADE_FAILED_BANNER_DISMISSED_AT: (ref: string) =>
`project-upgrade-failed-banner-dismissed-at-${ref}`,
UNIFIED_LOGS_BANNER_DISMISSED: 'unified-logs-banner-dismissed',
UNIFIED_LOGS_SIDEBAR_BANNER_DISMISSED: 'unified-logs-sidebar-banner-dismissed',
STORAGE_PUBLIC_BUCKET_SELECT_POLICY_WARNING_DISMISSED: (ref: string, bucketId: string) =>
Expand Down
108 changes: 108 additions & 0 deletions scripts/waitForVercelDocsPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Vercel's GitHub App has stopped writing GitHub Deployment objects since
// 2026-02-17 (broken app auth), so polling the Deployments API (as
// vercel/wait-for-deployment-action does) times out even though the docs
// preview builds fine. Poll the "Vercel – docs" commit status instead, then
// resolve the actual preview URL via Vercel's own deployments API.
const { appendFileSync } = require('fs')

const STATUS_CONTEXT = 'Vercel – docs'
const TIMEOUT_MS = 900_000
const POLL_INTERVAL_MS = 15_000

async function fetchLatestStatus(repository, sha, githubToken) {
const url = `https://api.github.com/repos/${repository}/commits/${sha}/statuses`
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${githubToken}`,
Accept: 'application/vnd.github+json',
},
})

if (!response.ok) {
throw new Error(`Failed to fetch commit statuses: ${response.status} ${response.statusText}`)
}

const statuses = await response.json()

return statuses
.filter((status) => status.context === STATUS_CONTEXT)
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0]
}

async function resolveDeploymentUrl(targetUrl, vercelToken, teamId) {
const rawId = targetUrl.split('/').filter(Boolean).pop()
if (!rawId) {
throw new Error(`Could not parse a deployment ID from target_url: ${targetUrl}`)
}
const deploymentId = rawId.startsWith('dpl_') ? rawId : `dpl_${rawId}`

const url = teamId
? `https://api.vercel.com/v13/deployments/${deploymentId}?teamId=${teamId}`
: `https://api.vercel.com/v13/deployments/${deploymentId}`

const response = await fetch(url, {
headers: { Authorization: `Bearer ${vercelToken}` },
})

if (!response.ok) {
throw new Error(
`Failed to resolve Vercel deployment ${deploymentId}: ${response.status} ${response.statusText}`
)
}

const deployment = await response.json()
return `https://${deployment.url}`
}

function writeOutput(name, value) {
const outputFile = process.env.GITHUB_OUTPUT
if (!outputFile) {
throw new Error('GITHUB_OUTPUT environment variable is required')
}
appendFileSync(outputFile, `${name}=${value}\n`)
}

async function main() {
const repository = process.env.GITHUB_REPOSITORY
const sha = process.env.HEAD_SHA
const githubToken = process.env.GITHUB_TOKEN
const vercelToken = process.env.VERCEL_TOKEN
const teamId = process.env.VERCEL_TEAM_ID

if (!repository) throw new Error('GITHUB_REPOSITORY environment variable is required')
if (!sha) throw new Error('HEAD_SHA environment variable is required')
if (!githubToken) throw new Error('GITHUB_TOKEN environment variable is required')
if (!vercelToken) throw new Error('VERCEL_TOKEN environment variable is required')

const start = Date.now()

for (;;) {
const latest = await fetchLatestStatus(repository, sha, githubToken)

if (latest?.state === 'success') {
if (!latest.target_url) {
throw new Error(
'Vercel docs commit status succeeded but had no target_url to resolve a deployment from'
)
}
const deploymentUrl = await resolveDeploymentUrl(latest.target_url, vercelToken, teamId)
writeOutput('deployment-url', deploymentUrl)
return
}

if (latest?.state === 'failure' || latest?.state === 'error') {
throw new Error(`Vercel docs deployment failed (commit status: ${latest.state})`)
}

if (Date.now() - start > TIMEOUT_MS) {
throw new Error('Timed out after 900s waiting for the Vercel docs preview deployment')
}

await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))
}
}

main().catch((error) => {
console.error('Fatal error:', error)
process.exit(1)
})
Loading