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
40 changes: 38 additions & 2 deletions .github/workflows/studio-lint-ratchet-decrease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
permission-pull-requests: write

- name: Decrease ESLint ratchet baselines and open PR
id: decrease-baselines
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
Expand All @@ -69,6 +70,7 @@ jobs:

if git diff --quiet; then
echo "No baseline updates detected."
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi

Expand All @@ -81,11 +83,45 @@ jobs:

pr_url=$(gh pr list --state open --head "$BRANCH" --json url --jq '.[0].url // ""' 2>/dev/null || echo "")
if [ -z "$pr_url" ]; then
gh pr create \
pr_url=$(gh pr create \
--title "[bot] Decrease ESLint ratchet baselines" \
--body "Automated weekly decrease of ESLint ratchet baselines." \
--base "$DEFAULT_BRANCH" \
--head "$BRANCH"
--head "$BRANCH")
else
gh pr comment "$pr_url" --body "Updated ESLint ratchet baselines with the latest weekly decreases."
fi

echo "changed=true" >> "$GITHUB_OUTPUT"
echo "pr_url=$pr_url" >> "$GITHUB_OUTPUT"

- name: 'Notify #team-frontend about rules that hit a zero baseline'
if: steps.decrease-baselines.outputs.changed == 'true'
env:
PR_URL: ${{ steps.decrease-baselines.outputs.pr_url }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DASHBOARD_WEBHOOK_URL }}
run: |
set -euo pipefail

zero_rules=$(jq -r '.rules | to_entries | map(select(.value == 0) | .key) | join(", ")' apps/studio/.github/eslint-rule-baselines.json)

if [ -z "$zero_rules" ]; then
echo "No rules dropped to a baseline of 0; nothing to notify."
exit 0
fi

if [ -z "${SLACK_WEBHOOK_URL:-}" ]; then
echo "::warning::SLACK_DASHBOARD_WEBHOOK_URL secret is not set; skipping Slack notification for zero-baseline rules: $zero_rules"
exit 0
fi

pr_number="${PR_URL##*/}"
text="<@U0A1BRW39PC> the weekly ratchet-baseline job just dropped these rules to 0 in <${PR_URL}|#${pr_number}>: ${zero_rules}. Can you remove them from ratchet tracking and bump their ESLint severity to \"error\"?"

payload=$(jq -n --arg text "$text" '{text: $text}')

if ! curl --fail --silent --show-error -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "$payload"; then
echo "::warning::Failed to post Slack notification for zero-baseline rules: $zero_rules"
fi
19 changes: 11 additions & 8 deletions apps/docs/components/StepHikeCompact/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ interface IStep {
}

interface IStepHikeCompactSubcomponents {
Step: FC<IStep>
Details: FC<IDetails>
Code: FC<ICode>
Step: FC<PropsWithChildren<IStep>>
Details: FC<PropsWithChildren<IDetails>>
Code: FC<PropsWithChildren<ICode>>
}
interface IDetails {
title?: string
fullWidth?: boolean
}

interface ICode {}
interface ICode {
className?: string
}

interface IStepHikeCompact {
title: string
Expand Down Expand Up @@ -65,7 +67,7 @@ const Step: FC<PropsWithChildren<IStep>> = ({ children, title, step }) => {
className="border bg-surface-100
border-control flex items-center justify-center rounded-full
w-6 h-6 text-xs text-foreground font-normal font-mono
dropshadow-sm
drop-shadow-sm
"
>
{step}
Expand Down Expand Up @@ -107,7 +109,7 @@ const Details: FC<PropsWithChildren<IDetails>> = ({ children, title, fullWidth =
)
}

const Code: FC<PropsWithChildren<ICode>> = ({ children }) => {
const Code: FC<PropsWithChildren<ICode>> = ({ children, className }) => {
// Not `not-prose`: steps interleave labels and admonitions with their code samples, and
// stripping prose leaves that text unstyled and flush against the samples.
return (
Expand All @@ -118,7 +120,8 @@ const Code: FC<PropsWithChildren<ICode>> = ({ children }) => {
// `Step` spaces the block as a whole, so samples don't carry margins of their own...
'[&_.shiki]:!my-0 [&_.shiki-wrapper]:!my-0',
// ...but back-to-back samples have no prose between them to separate them.
'[&_.shiki+.shiki]:!mt-6'
'[&_.shiki+.shiki]:!mt-6',
className
)}
>
{children}
Expand All @@ -129,4 +132,4 @@ const Code: FC<PropsWithChildren<ICode>> = ({ children }) => {
StepHikeCompact.Step = Step
StepHikeCompact.Details = Details
StepHikeCompact.Code = Code
export default StepHikeCompact
export { StepHikeCompact }
8 changes: 5 additions & 3 deletions apps/docs/data/monitoring-agents.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export type MonitoringAgentHarnessSetup = {
const WEEKDAY_LABELS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] as const
const DAY_LABELS = ['12am', '6am', '12pm', '6pm'] as const

export const AGENT_PROMPT_ANCHOR = 'agent-prompt'

const MCP_STEP =
'Connect the [Supabase MCP server](/docs/guides/ai-tools/mcp) with `project_ref` and `read_only=true`.'

Expand Down Expand Up @@ -103,7 +105,7 @@ export function getMonitoringAgentHarnesses(agent: MonitoringAgent): MonitoringA
isSubHourly
? 'In the Claude Code Desktop app, open **Routines**, click **New routine**, and choose **Local**.'
: 'Open [Claude routines](https://claude.ai/code/routines) or run `/schedule` in Claude Code.',
`Name it ${agent.name}. Paste the prompt. Set the schedule to ${cadence}.`,
`Name it ${agent.name}. Paste the [prompt](#${AGENT_PROMPT_ANCHOR}). Set the schedule to ${cadence}.`,
],
note: isSubHourly
? 'Cloud routines have a 1-hour minimum. Use a [Desktop scheduled task](https://code.claude.com/docs/en/desktop-scheduled-tasks) for this cadence.'
Expand All @@ -119,7 +121,7 @@ export function getMonitoringAgentHarnesses(agent: MonitoringAgent): MonitoringA
steps: [
MCP_STEP,
'Open **Scheduled** in the ChatGPT desktop app, or ask Codex to create a standalone scheduled task.',
`Name it ${agent.name}. Paste the prompt. Set the schedule to ${cadence}. Each run should start a new chat.`,
`Name it ${agent.name}. Paste the [prompt](#${AGENT_PROMPT_ANCHOR}). Set the schedule to ${cadence}. Each run should start a new chat.`,
],
},
{
Expand All @@ -132,7 +134,7 @@ export function getMonitoringAgentHarnesses(agent: MonitoringAgent): MonitoringA
steps: [
MCP_STEP,
'Create an automation in the Agents Window, at [cursor.com/automations](https://cursor.com/automations), or with the `/automate` skill.',
`Name it ${agent.name}. Use a scheduled trigger (${cadence}, cron \`${cron}\`). Paste the prompt. Keep the agent read-only, with no repository.`,
`Name it ${agent.name}. Use a scheduled trigger (${cadence}, cron \`${cron}\`). Paste the [prompt](#${AGENT_PROMPT_ANCHOR}). Keep the agent read-only, with no repository.`,
],
},
]
Expand Down
98 changes: 98 additions & 0 deletions apps/docs/docs/ref/server/usage-examples.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
id: usage-examples
title: Usage examples
---

Each example is a Fetch handler. Deno, Bun, and Supabase Edge Functions run the `export default { fetch }` shape as is. Cloudflare Workers also need the `nodejs_compat` flag or the `env` option, so `withSupabase` can read your project's URL and keys. On Node, use an adapter or the primitives with your framework.

### Protect an endpoint with a user JWT

<RefSubLayout.EducationRow>
<RefSubLayout.Details>

`withSupabase` wraps your handler. With `auth: 'user'`, it checks the caller's JWT before your handler runs. A request without valid credentials gets a JSON error response, and your handler never runs.

`withSupabase` also answers every `OPTIONS` request with `204` and adds CORS headers to every response. Set `cors: 'disabled'` when your framework handles CORS.

`ctx.supabase` is scoped to the caller, so Row Level Security policies apply. `ctx.supabaseAdmin` bypasses RLS. Use it only for work that needs full database access.

</RefSubLayout.Details>

<RefSubLayout.Examples>

```ts
import { withSupabase } from '@supabase/server'

export default {
fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
const { data } = await ctx.supabase.from('todos').select()
return Response.json(data)
}),
}
```

</RefSubLayout.Examples>
</RefSubLayout.EducationRow>

### Serve a public endpoint

<RefSubLayout.EducationRow>
<RefSubLayout.Details>

`auth: 'none'` lets every request through. `ctx.userClaims` is `null`, and `ctx.supabase` runs as an anonymous client.

Supabase Edge Functions check for a valid JWT on every request by default. For a function that uses `auth: 'none'`, `auth: 'publishable'`, or `auth: 'secret'`, turn that check off in `supabase/config.toml`. The `auth` setting on `withSupabase` then decides who gets in.

</RefSubLayout.Details>

<RefSubLayout.Examples>

```ts
import { withSupabase } from '@supabase/server'

export default {
fetch: withSupabase({ auth: 'none' }, async () => {
return Response.json({ status: 'ok', time: new Date().toISOString() })
}),
}
```

```toml supabase/config.toml
[functions.my-function]
verify_jwt = false
```

</RefSubLayout.Examples>
</RefSubLayout.EducationRow>

### Build the context yourself

<RefSubLayout.EducationRow>
<RefSubLayout.Details>

`createSupabaseContext` runs the same auth checks as `withSupabase` but returns `{ data, error }` instead of a response. Use it inside a framework route handler, or anywhere you want to shape the error response yourself.

On success, `data` is the same `SupabaseContext` that `withSupabase` passes to your handler. On failure, `error.status` holds the HTTP status to send. `createSupabaseContext` does not handle CORS.

</RefSubLayout.Details>

<RefSubLayout.Examples>

```ts
import { createSupabaseContext } from '@supabase/server'

export default {
fetch: async (req: Request) => {
const { data: ctx, error } = await createSupabaseContext(req, { auth: 'user' })
if (error) {
return Response.json(error.toJSON(), { status: error.status })
}

const { data } = await ctx.supabase.from('todos').select()
return Response.json(data)
},
}
```

</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
49 changes: 36 additions & 13 deletions apps/docs/features/directives/CodeSample.client.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import Link from 'next/link'
import { type PropsWithChildren, type ReactNode } from 'react'
import { type CSSProperties, type PropsWithChildren, type ReactNode } from 'react'
import {
cn,
DropdownMenu,
Expand All @@ -26,18 +26,27 @@ interface SingleSourceProps {

interface SourceFrameProps {
footer: ReactNode
notchWidth?: number
}

const SOURCE_FOOTER_CLASSES = cn(
'not-prose absolute right-0 bottom-0 z-1 flex h-8 w-29.25 items-center gap-2 px-3 whitespace-nowrap',
export const SOURCE_FOOTER_CLASSES = cn(
'not-prose absolute right-0 bottom-0 z-1 flex h-8 w-(--source-notch-width) items-center gap-2 px-3 whitespace-nowrap',
'text-xs text-foreground-lighter no-underline transition-colors hover:text-foreground',
'focus-inset rounded-md'
)

const SOURCE_NOTCH_OUTLINE =
'M0 39.5A7.5 7.5 0 0 0 7.5 32V16A8.5 8.5 0 0 1 16 7.5H117A7.5 7.5 0 0 0 124.5 0'
const DEFAULT_NOTCH_WIDTH = 117

const SOURCE_NOTCH_FOCUS_RING = 'M0 41A9 9 0 0 0 9 32V16A7 7 0 0 1 16 9H117A9 9 0 0 0 126 0'
const getNotchOutline = (width: number): string =>
`M0 39.5A7.5 7.5 0 0 0 7.5 32V16A8.5 8.5 0 0 1 16 7.5H${width}A7.5 7.5 0 0 0 ${width + 7.5} 0`

const getNotchFocusRing = (width: number): string =>
`M0 41A9 9 0 0 0 9 32V16A7 7 0 0 1 16 9H${width}A9 9 0 0 0 ${width + 9} 0`

const getNotchMask = (width: number): string =>
`url("data:image/svg+xml,${encodeURIComponent(
`<svg xmlns='http://www.w3.org/2000/svg' width='${width + 10}' height='42'><path d='M0 40A8 8 0 0 0 8 32V16A8 8 0 0 1 16 8H${width}A8 8 0 0 0 ${width + 8} 0H${width + 10}V42H0Z'/></svg>`
)}")`

const getSourcePath = (source: string | URL): string => {
const [, org, repo, , , ...path] = new URL(source).pathname.split('/')
Expand Down Expand Up @@ -123,24 +132,38 @@ function SingleSource({ children, source }: PropsWithChildren<SingleSourceProps>
)
}

function SourceFrame({ children, footer }: PropsWithChildren<SourceFrameProps>) {
export function SourceFrame({
children,
footer,
notchWidth = DEFAULT_NOTCH_WIDTH,
}: PropsWithChildren<SourceFrameProps>) {
const outline = getNotchOutline(notchWidth)
const notchStyle = {
'--source-notch-width': `${notchWidth}px`,
'--source-notch-mask': getNotchMask(notchWidth),
'--source-notch-mask-width': `${notchWidth + 10}px`,
} as CSSProperties

return (
<div className="group/source code-sample-source shiki-wrapper relative w-full [&_.shiki]:pb-8 [&_.shiki]:shadow-none!">
<div
style={notchStyle}
className="group/source code-sample-source shiki-wrapper relative w-full [&_.shiki]:pb-8 [&_.shiki]:shadow-none!"
>
<div className="codeblock-drop-shadow relative has-[.code-scroll:focus-visible]:z-1">
{children}
</div>
<svg
aria-hidden
width="125"
width={notchWidth + 8}
height="40"
viewBox="0 0 125 40"
viewBox={`0 0 ${notchWidth + 8} 40`}
fill="none"
className="pointer-events-none absolute right-0 bottom-0 z-1 overflow-visible"
>
<path d={SOURCE_NOTCH_OUTLINE} className="stroke-background-200" />
<path d={SOURCE_NOTCH_OUTLINE} className="stroke-border" />
<path d={outline} className="stroke-background-200" />
<path d={outline} className="stroke-border" />
<path
d={SOURCE_NOTCH_FOCUS_RING}
d={getNotchFocusRing(notchWidth)}
strokeWidth={2}
className="hidden stroke-ring group-has-[.code-scroll:focus-visible]/source:inline"
/>
Expand Down
17 changes: 14 additions & 3 deletions apps/docs/features/directives/CodeTabs.components.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Children, isValidElement, type PropsWithChildren } from 'react'
import { Children, isValidElement, type PropsWithChildren, type ReactNode } from 'react'
import { cn, TabsIndicator, TabsList, Tabs as TabsRoot, TabsTrigger } from 'ui'

interface CodeTabPanelProps {
id: string
label?: string
icon?: ReactNode
}

interface CodeTabsProps {
value?: string
onValueChange?: (value: string) => void
}

export function NamedCodeBlock({ name, children }: PropsWithChildren<{ name: string }>) {
Expand Down Expand Up @@ -31,12 +37,14 @@ export function NamedCodeBlock({ name, children }: PropsWithChildren<{ name: str
)
}

export function CodeTabs({ children }: PropsWithChildren) {
export function CodeTabs({ children, value, onValueChange }: PropsWithChildren<CodeTabsProps>) {
const tabs = Children.toArray(children).filter(isValidElement<CodeTabPanelProps>)

return (
<TabsRoot
defaultValue={tabs[0]?.props.id}
value={value}
onValueChange={onValueChange}
className="group/code-tabs shiki-wrapper w-full isolate [&_.shiki]:my-0!"
>
<TabsList
Expand All @@ -51,7 +59,10 @@ export function CodeTabs({ children }: PropsWithChildren) {
value={tab.props.id}
className="relative z-1 rounded-md py-2 text-xs data-[state=active]:shadow-none"
>
<span className="px-3">{tab.props.label ?? tab.props.id}</span>
<span className="flex items-center gap-2 px-3 [&_img]:size-3.5">
{tab.props.icon}
{tab.props.label ?? tab.props.id}
</span>
</TabsTrigger>
))}
<TabsIndicator
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/features/docs/MdxBase.shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { ProjectConfigVariables } from '~/components/ProjectConfigVariables'
import { RealtimeLimitsEstimator } from '~/components/RealtimeLimitsEstimator'
import { RegionsList, SmartRegionsList } from '~/components/RegionsList'
import { SharedData } from '~/components/SharedData'
import StepHikeCompact from '~/components/StepHikeCompact'
import { StepHikeCompact } from '~/components/StepHikeCompact'
import Table from '~/components/Table'
import { TerraformProviderSchema } from '~/components/TerraformProviderSchema'
import { WrapperDashboardIntegration } from '~/components/WrapperDashboardIntegration'
Expand Down
Loading
Loading