diff --git a/.github/workflows/studio-lint-ratchet-decrease.yml b/.github/workflows/studio-lint-ratchet-decrease.yml index 0b0faabf5f356..71ac4bcbec6fe 100644 --- a/.github/workflows/studio-lint-ratchet-decrease.yml +++ b/.github/workflows/studio-lint-ratchet-decrease.yml @@ -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 }} @@ -69,6 +70,7 @@ jobs: if git diff --quiet; then echo "No baseline updates detected." + echo "changed=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -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 diff --git a/apps/docs/components/StepHikeCompact/index.tsx b/apps/docs/components/StepHikeCompact/index.tsx index 33e15d219cbfe..1b6ac3eb8e62c 100644 --- a/apps/docs/components/StepHikeCompact/index.tsx +++ b/apps/docs/components/StepHikeCompact/index.tsx @@ -7,16 +7,18 @@ interface IStep { } interface IStepHikeCompactSubcomponents { - Step: FC - Details: FC - Code: FC + Step: FC> + Details: FC> + Code: FC> } interface IDetails { title?: string fullWidth?: boolean } -interface ICode {} +interface ICode { + className?: string +} interface IStepHikeCompact { title: string @@ -65,7 +67,7 @@ const Step: FC> = ({ 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} @@ -107,7 +109,7 @@ const Details: FC> = ({ children, title, fullWidth = ) } -const Code: FC> = ({ children }) => { +const Code: FC> = ({ 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 ( @@ -118,7 +120,8 @@ const Code: FC> = ({ 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} @@ -129,4 +132,4 @@ const Code: FC> = ({ children }) => { StepHikeCompact.Step = Step StepHikeCompact.Details = Details StepHikeCompact.Code = Code -export default StepHikeCompact +export { StepHikeCompact } diff --git a/apps/docs/data/monitoring-agents.utils.ts b/apps/docs/data/monitoring-agents.utils.ts index 5c084726600fc..b6c975bf19a30 100644 --- a/apps/docs/data/monitoring-agents.utils.ts +++ b/apps/docs/data/monitoring-agents.utils.ts @@ -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`.' @@ -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.' @@ -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.`, ], }, { @@ -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.`, ], }, ] diff --git a/apps/docs/docs/ref/server/usage-examples.mdx b/apps/docs/docs/ref/server/usage-examples.mdx new file mode 100644 index 0000000000000..416a6461d448e --- /dev/null +++ b/apps/docs/docs/ref/server/usage-examples.mdx @@ -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 + + + + + `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. + + + + + + ```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) + }), + } + ``` + + + + +### Serve a public endpoint + + + + + `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. + + + + + + ```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 + ``` + + + + +### Build the context yourself + + + + + `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. + + + + + + ```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) + }, + } + ``` + + + diff --git a/apps/docs/features/directives/CodeSample.client.tsx b/apps/docs/features/directives/CodeSample.client.tsx index 8e6f2c15a9fc5..e609d3236ca40 100644 --- a/apps/docs/features/directives/CodeSample.client.tsx +++ b/apps/docs/features/directives/CodeSample.client.tsx @@ -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, @@ -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( + `` + )}")` const getSourcePath = (source: string | URL): string => { const [, org, repo, , , ...path] = new URL(source).pathname.split('/') @@ -123,24 +132,38 @@ function SingleSource({ children, source }: PropsWithChildren ) } -function SourceFrame({ children, footer }: PropsWithChildren) { +export function SourceFrame({ + children, + footer, + notchWidth = DEFAULT_NOTCH_WIDTH, +}: PropsWithChildren) { + 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 ( -
+
{children}
- - + + diff --git a/apps/docs/features/directives/CodeTabs.components.tsx b/apps/docs/features/directives/CodeTabs.components.tsx index 21ee142894edb..5b754e0facbb2 100644 --- a/apps/docs/features/directives/CodeTabs.components.tsx +++ b/apps/docs/features/directives/CodeTabs.components.tsx @@ -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 }>) { @@ -31,12 +37,14 @@ export function NamedCodeBlock({ name, children }: PropsWithChildren<{ name: str ) } -export function CodeTabs({ children }: PropsWithChildren) { +export function CodeTabs({ children, value, onValueChange }: PropsWithChildren) { const tabs = Children.toArray(children).filter(isValidElement) return ( - {tab.props.label ?? tab.props.id} + + {tab.props.icon} + {tab.props.label ?? tab.props.id} + ))} ) => { + const target = document.getElementById(event.currentTarget.hash.slice(1)) + if (!target) return + + event.preventDefault() + const { hash } = event.currentTarget + if (window.location.hash !== hash) window.history.pushState(null, '', hash) + + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + target.scrollIntoView({ behavior: prefersReducedMotion ? 'auto' : 'smooth', block: 'center' }) + target.focus({ preventScroll: true }) + + const brand = getComputedStyle(target).getPropertyValue('--brand-default') + target.animate([{ outlineColor: `hsl(${brand})` }, { outlineColor: `hsl(${brand} / 0)` }], { + duration: PROMPT_FLASH_DURATION, + easing: 'ease-out', + }) +} + const markdownComponents = { p: ({ children }: { children?: ReactNode }) => <>{children}, a: ({ href, children }: { href?: string; children?: ReactNode }) => { if (!href) return <>{children} + if (href.startsWith('#')) { + return ( + + {children} + + ) + } const external = /^(?:[a-z][a-z0-9+\-.]*:|\/\/)/i.test(href) return ( harness.key), + queryGroup: 'agent-setup', + }) + const [selectedHarness, setSelectedHarness] = useState(harnesses[0].key) + const activeHarness = queryTab ?? selectedHarness + + const handleHarnessChange = (key: string) => { + setSelectedHarness(key) + onTabSelected(key) + } + + return ( + + + + +
+ +
+
+
+ + + + + {harnesses.map((harness) => ( + } + > + + + View docs +
+ } + > + + + + ))} + + + + + ) +} + +function HarnessIcon({ harness }: HarnessProps) { + const getSrc = (useDarkVariant: boolean) => + getMcpClientIconSrc({ + icon: harness.icon, + useDarkVariant, + hasDistinctDarkIcon: harness.hasDistinctDarkIcon, + }) + + if (!harness.hasDistinctDarkIcon) { + return + } + return ( <> + + + + ) +} + +function HarnessBody({ harness }: HarnessProps) { + return ( +

{harness.intro}

    {harness.steps.map((step) => ( @@ -53,54 +189,7 @@ function HarnessBody({ harness }: { harness: MonitoringAgentHarnessSetup }) { {harness.note}

    )} -

    - - {harness.label} docs - -

    - - ) -} - -function AgentSetup({ id }: AgentSetupProps) { - const agent = getMonitoringAgent(id) - const harnesses = getMonitoringAgentHarnesses(agent) - const { resolvedTheme } = useTheme() - const theme = resolvedTheme?.includes('dark') ? 'dark' : 'light' - - return ( - - }> - - - {harnesses.map((harness) => ( - - } - > - - - ))} - +
) } diff --git a/apps/docs/internals/markdown-schema/AgentSetup.test.ts b/apps/docs/internals/markdown-schema/AgentSetup.test.ts index f11a2d1e86af9..e054a21a64a8d 100644 --- a/apps/docs/internals/markdown-schema/AgentSetup.test.ts +++ b/apps/docs/internals/markdown-schema/AgentSetup.test.ts @@ -21,7 +21,8 @@ describe('AgentSetup markdown schema', () => { it('serializes the prompt and harness setup for a registered agent', () => { const markdown = AgentSetup({ props: { id: 'health' } }) - expect(markdown).toContain('**Prompt**') + expect(markdown).toContain('**Step 1: Copy the prompt**') + expect(markdown).toContain('**Step 2: Schedule it in your agent**') expect(markdown).toContain('You are "Health monitor"') expect(markdown).toContain('```text') expect(markdown).toContain('**Claude**') @@ -29,6 +30,8 @@ describe('AgentSetup markdown schema', () => { expect(markdown).toContain('**Cursor**') expect(markdown).toContain('claude.ai/code/routines') expect(markdown).toContain('`0 * * * *`') + expect(markdown).toContain('Paste the prompt.') + expect(markdown).not.toContain('](#') expect(markdown).toContain('[Claude docs](https://code.claude.com/docs/en/routines)') expect(markdown).toContain('[Codex docs](https://developers.openai.com/codex/app/automations)') expect(markdown).toContain('[Cursor docs](https://cursor.com/docs/cloud-agent/automations)') diff --git a/apps/docs/internals/markdown-schema/AgentSetup.ts b/apps/docs/internals/markdown-schema/AgentSetup.ts index 670b2d7163068..ce57bd743a720 100644 --- a/apps/docs/internals/markdown-schema/AgentSetup.ts +++ b/apps/docs/internals/markdown-schema/AgentSetup.ts @@ -9,8 +9,10 @@ type HandlerContext = { props: Record } +const IN_PAGE_LINK = /\[([^\]]+)\]\(#[^)]+\)/g + function renderMarkdownSteps(steps: string[]): string { - return steps.map((step, index) => `${index + 1}. ${step}`).join('\n') + return steps.map((step, index) => `${index + 1}. ${step.replace(IN_PAGE_LINK, '$1')}`).join('\n') } export function AgentSetup({ props }: HandlerContext): string { @@ -19,7 +21,8 @@ export function AgentSetup({ props }: HandlerContext): string { const harnesses = getMonitoringAgentHarnesses(agent) const sections = [ - `**Prompt**\n\n${toMarkdown({ type: 'code', lang: 'text', value: prompt }).trimEnd()}`, + `**Step 1: Copy the prompt**\n\n${toMarkdown({ type: 'code', lang: 'text', value: prompt }).trimEnd()}`, + '**Step 2: Schedule it in your agent**', ...harnesses.map((harness) => { const parts = [`**${harness.label}**`, harness.intro, renderMarkdownSteps(harness.steps)] if (harness.note) parts.push(harness.note) diff --git a/apps/docs/spec/reference/server/v1/config.json b/apps/docs/spec/reference/server/v1/config.json index dc086ebe58f88..38d8544d1068f 100644 --- a/apps/docs/spec/reference/server/v1/config.json +++ b/apps/docs/spec/reference/server/v1/config.json @@ -1,5 +1,5 @@ { "categoryOrder": ["Middleware", "Primitives", "Adapters", "Errors", "Types"], - "partialsOrder": ["introduction", "installing"], + "partialsOrder": ["introduction", "installing", "usage-examples"], "navigationPrefixes": {} } diff --git a/apps/docs/spec/reference/server/v1/partials/usage-examples.mdx b/apps/docs/spec/reference/server/v1/partials/usage-examples.mdx new file mode 100644 index 0000000000000..416a6461d448e --- /dev/null +++ b/apps/docs/spec/reference/server/v1/partials/usage-examples.mdx @@ -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 + + + + + `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. + + + + + + ```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) + }), + } + ``` + + + + +### Serve a public endpoint + + + + + `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. + + + + + + ```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 + ``` + + + + +### Build the context yourself + + + + + `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. + + + + + + ```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) + }, + } + ``` + + + diff --git a/apps/docs/styles/code-block.css b/apps/docs/styles/code-block.css index 7db2cf9c248f8..762e9e829c348 100644 --- a/apps/docs/styles/code-block.css +++ b/apps/docs/styles/code-block.css @@ -130,11 +130,10 @@ filter: var(--drop-shadow-codeblock, none); } -.code-sample-source .shiki { +.code-sample-source :is(.shiki, .code-sample-surface) { mask: linear-gradient(#000 0 0), - url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='127' height='42'%3E%3Cpath d='M0 40A8 8 0 0 0 8 32V16A8 8 0 0 1 16 8H117A8 8 0 0 0 125 0H127V42H0Z'/%3E%3C/svg%3E") - right -2px bottom -2px / 127px 42px no-repeat; + var(--source-notch-mask) right -2px bottom -2px / var(--source-notch-mask-width) 42px no-repeat; mask-composite: exclude; mask-clip: no-clip; } diff --git a/apps/studio/.github/eslint-rule-baselines.json b/apps/studio/.github/eslint-rule-baselines.json index 26a486ececdf2..6d063265ab401 100644 --- a/apps/studio/.github/eslint-rule-baselines.json +++ b/apps/studio/.github/eslint-rule-baselines.json @@ -1,27 +1,17 @@ { "rules": { - "react-hooks/exhaustive-deps": 160, + "react-hooks/exhaustive-deps": 156, "import/no-anonymous-default-export": 57, - "@tanstack/query/exhaustive-deps": 89, - "@typescript-eslint/no-explicit-any": 875, - "no-restricted-imports": 0, - "no-restricted-exports": 193, - "react/no-unstable-nested-components": 36, - "jsx-a11y/aria-props": 0, - "jsx-a11y/aria-proptypes": 0, - "jsx-a11y/role-supports-aria-props": 0, - "jsx-a11y/anchor-has-content": 0, - "jsx-a11y/control-has-associated-label": 248, - "jsx-a11y/label-has-associated-control": 36, - "jsx-a11y/aria-role": 0, - "jsx-a11y/no-redundant-roles": 4, - "jsx-a11y/no-aria-hidden-on-focusable": 0, - "jsx-a11y/tabindex-no-positive": 0, - "jsx-a11y/anchor-is-valid": 7, + "@tanstack/query/exhaustive-deps": 87, + "@typescript-eslint/no-explicit-any": 799, + "no-restricted-exports": 176, + "react/no-unstable-nested-components": 34, + "jsx-a11y/control-has-associated-label": 178, + "jsx-a11y/label-has-associated-control": 33, + "jsx-a11y/no-redundant-roles": 2, + "jsx-a11y/anchor-is-valid": 6, "jsx-a11y/heading-has-content": 2, - "jsx-a11y/no-distracting-elements": 0, - "valtio/state-snapshot-rule": 1, - "react-hook-form/no-use-watch": 76 + "valtio/state-snapshot-rule": 1 }, "ruleFiles": { "react-hooks/exhaustive-deps": { @@ -54,14 +44,12 @@ "components/interfaces/Database/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx": 1, "components/interfaces/Database/Policies/PolicyEditorPanel/RLSCodeEditor.tsx": 1, "components/interfaces/Database/Replication/ReplicationDiagram/index.tsx": 1, - "components/interfaces/DiskManagement/DiskManagementForm.tsx": 1, + "components/interfaces/Explorer/hooks.ts": 2, "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx": 1, - "components/interfaces/Integrations/CronJobs/CreateCronJobSheet/CreateCronJobSheet.tsx": 1, "components/interfaces/Integrations/Queues/QueuesSettings.tsx": 1, "components/interfaces/Integrations/Queues/SingleQueue/QueueSettings.tsx": 1, "components/interfaces/Integrations/Queues/SingleQueue/SendMessageModal.tsx": 1, "components/interfaces/Observability/DatabaseInfrastructureSection.tsx": 1, - "components/interfaces/Organization/BillingSettings/BillingEmail.tsx": 1, "components/interfaces/Organization/BillingSettings/CreditTopUp.tsx": 1, "components/interfaces/Organization/BillingSettings/Subscription/PaymentMethodSelection.tsx": 2, "components/interfaces/Organization/Usage/Compute.tsx": 1, @@ -72,19 +60,14 @@ "components/interfaces/Reports/ReportBlock/ChartBlock.tsx": 1, "components/interfaces/Reports/ReportFilterBar.tsx": 1, "components/interfaces/Reports/Reports.tsx": 1, - "components/ui/AIEditor/AskAIWidget.tsx": 1, - "components/ui/AIEditor/InlineWidget.tsx": 2, "components/interfaces/SQLEditor/MoveQueryModal.tsx": 1, - "components/interfaces/SQLEditor/OngoingQueriesPanel.tsx": 1, "components/interfaces/SQLEditor/useSnippetIdentity.ts": 1, "components/interfaces/SQLEditor/useSnippetTitleGenerator.ts": 1, "components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx": 1, "components/interfaces/Settings/Addons/IPv4SidePanel.tsx": 1, "components/interfaces/Settings/Addons/PITRSidePanel.tsx": 1, "components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx": 1, - "components/interfaces/Settings/Database/SSLConfiguration.tsx": 1, "components/interfaces/Settings/General/ComplianceConfig/ProjectComplianceMode.tsx": 1, - "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx": 1, "components/interfaces/Settings/Logs/LogsPreviewer.tsx": 2, "components/interfaces/Settings/Logs/LogsQueryPanel.tsx": 1, "components/interfaces/Settings/Logs/PreviewFilterPanel.tsx": 1, @@ -99,12 +82,16 @@ "components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/TextEditor.tsx": 2, "components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx": 1, "components/layouts/ProjectLayout/ConnectingState.tsx": 1, - "components/layouts/ProjectLayout/PausedState/PauseDisabledState.tsx": 1, + "components/layouts/ProjectLayout/PausedState/DownloadBackupsSection.tsx": 1, "components/layouts/ProjectLayout/index.tsx": 1, + "components/layouts/SQLEditorLayout/SQLEditorNavV2/LogsSnippetsSection.tsx": 1, "components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx": 4, "components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx": 2, "components/layouts/SignInLayout/SignInLayout.tsx": 1, "components/layouts/Tabs/Tabs.utils.ts": 2, + "components/ui/AIAssistantPanel/AIAssistant.tsx": 2, + "components/ui/AIEditor/AskAIWidget.tsx": 1, + "components/ui/AIEditor/InlineWidget.tsx": 2, "components/ui/Charts/ChartHighlightActions.tsx": 1, "components/ui/Charts/Charts.utils.tsx": 7, "components/ui/Charts/ComposedChartHandler.tsx": 1, @@ -120,12 +107,10 @@ "components/ui/FilterPopover.tsx": 1, "data/__templates/resource-query.ts": 1, "data/__templates/resources-query.ts": 1, - "data/reports/api-report-query.ts": 2, "data/reports/storage-report-query.ts": 2, "hooks/analytics/useFillTimeseriesSorted.ts": 3, "hooks/analytics/useLogsPreview.tsx": 4, "hooks/analytics/useLogsQuery.tsx": 1, - "hooks/analytics/useProjectUsageStats.tsx": 1, "hooks/analytics/useTimeseriesUnixToIso.ts": 2, "hooks/branches/useEdgeFunctionsDiff.ts": 1, "hooks/misc/useSchemaQueryState.ts": 1, @@ -135,7 +120,6 @@ "pages/forgot-password-mfa.tsx": 1, "pages/logout.tsx": 1, "pages/new/index.tsx": 1, - "pages/organizations.tsx": 1, "pages/project/[ref]/database/policies.tsx": 2, "pages/project/[ref]/editor/[id].tsx": 1, "pages/project/[ref]/integrations/[id]/index.tsx": 1, @@ -144,6 +128,7 @@ "pages/project/[ref]/sql/templates.tsx": 1, "state/role-impersonation-state.tsx": 1, "state/sidebar-manager-state.tsx": 1, + "state/storage-explorer.tsx": 1, "state/table-editor-table.tsx": 1 }, "import/no-anonymous-default-export": { @@ -273,8 +258,6 @@ "data/projects/project-detail-query.ts": 1, "data/projects/project-type-generation-query.ts": 1, "data/read-replicas/replica-lag-query.ts": 1, - "data/sql/ongoing-queries-query.ts": 1, - "data/storage/bucket-objects-infinite-query.ts": 1, "data/storage/iceberg-namespace-tables-query.ts": 1, "data/storage/public-buckets-with-select-policies-query.ts": 1, "data/table-editor/table-editor-query.ts": 1, @@ -305,7 +288,6 @@ "components/grid/utils/column.ts": 2, "components/grid/utils/gridColumns.tsx": 10, "components/interfaces/APIKeys/CreateNewAPIKeysButton.tsx": 2, - "components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceAccess.types.ts": 2, "components/interfaces/Auth/AuditLogsForm.tsx": 1, "components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.types.ts": 1, "components/interfaces/Auth/AuthProvidersForm/FormField.tsx": 1, @@ -316,7 +298,6 @@ "components/interfaces/Auth/Overview/OverviewTable.tsx": 1, "components/interfaces/Auth/Overview/OverviewUsage.constants.ts": 1, "components/interfaces/Auth/PerformanceSettingsForm.tsx": 2, - "components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx": 1, "components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx": 1, "components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx": 2, "components/interfaces/Auth/SiteUrl/SiteUrl.tsx": 1, @@ -335,13 +316,11 @@ "components/interfaces/Database/Backups/RestoreToNewProject/BackupsList.tsx": 1, "components/interfaces/Database/Functions/CreateFunction/FunctionEditor.tsx": 1, "components/interfaces/Database/Indexes/Indexes.tsx": 1, - "components/interfaces/Database/Migrations/Migrations.tsx": 1, "components/interfaces/Database/Policies/Policies.tsx": 1, "components/interfaces/Database/Policies/PolicyEditorPanel/PolicyTemplates.tsx": 1, "components/interfaces/Database/Policies/PolicyEditorPanel/index.tsx": 2, "components/interfaces/Database/Privileges/PrivilegesTable.tsx": 1, "components/interfaces/Database/Publications/PublicationsList.tsx": 5, - "components/interfaces/Database/Replication/DisablePipelinesDialog.tsx": 1, "components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.utils.tsx": 1, "components/interfaces/Database/RestoreToNewProject/RestoreToNewProject.tsx": 1, "components/interfaces/Docs/Description.tsx": 2, @@ -353,7 +332,6 @@ "components/interfaces/Functions/CommandRender.tsx": 4, "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx": 1, "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.utils.tsx": 3, - "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx": 1, "components/interfaces/Home/ProjectList/ProjectList.tsx": 1, "components/interfaces/Integrations/CronJobs/CreateCronJobSheet/CreateCronJobSheet.tsx": 1, "components/interfaces/Integrations/CronJobs/PreviousRunsTab.tsx": 2, @@ -381,11 +359,9 @@ "components/interfaces/Organization/BillingSettings/Subscription/PaymentMethodSelection.tsx": 1, "components/interfaces/Organization/BillingSettings/Subscription/UpgradeModal.tsx": 3, "components/interfaces/Organization/IntegrationSettings/SidePanelVercelProjectLinker.tsx": 1, - "components/interfaces/Organization/InvoicesSettings/InvoicesSettings.tsx": 2, + "components/interfaces/Organization/InvoicesSettings/InvoicesSettings.tsx": 1, "components/interfaces/Organization/OAuthApps/PublishAppSidePanel/index.tsx": 3, "components/interfaces/Organization/ProjectClaim/confirm.tsx": 1, - "components/interfaces/Organization/TeamSettings/MemberRow.tsx": 2, - "components/interfaces/Organization/TeamSettings/TeamSettings.tsx": 1, "components/interfaces/Organization/TeamSettings/UpdateRolesPanel/UpdateRolesConfirmationModal.tsx": 2, "components/interfaces/Organization/Usage/Usage.tsx": 1, "components/interfaces/Organization/Usage/Usage.utils.ts": 1, @@ -405,7 +381,6 @@ "components/interfaces/Realtime/Inspector/Messages.types.ts": 1, "components/interfaces/Realtime/Inspector/RealtimeFilterPopover/TableSelector.tsx": 1, "components/interfaces/Realtime/Inspector/useRealtimeMessages.ts": 10, - "components/interfaces/Realtime/RealtimeSettings.tsx": 2, "components/interfaces/Reports/GridResize.tsx": 2, "components/interfaces/Reports/MetricOptions.tsx": 1, "components/interfaces/Reports/ReportBlock/ChartBlock.tsx": 1, @@ -416,20 +391,15 @@ "components/interfaces/Reports/SharedAPIReport/SharedAPIReport.tsx": 3, "components/interfaces/Reports/v2/ReportChartUpsell.tsx": 1, "components/interfaces/Reports/v2/ReportChartV2.tsx": 1, - "components/interfaces/RoleImpersonationSelector/UserImpersonationSelector.tsx": 1, - "components/interfaces/SQLEditor/MoveQueryModal.tsx": 2, + "components/interfaces/SQLEditor/MoveQueryModal.tsx": 1, "components/interfaces/SQLEditor/RenameQueryModal.tsx": 2, - "components/interfaces/SQLEditor/SQLEditor.utils.ts": 1, "components/interfaces/SQLEditor/SQLTemplates/SQLExamples.tsx": 1, "components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx": 1, - "components/interfaces/SQLEditor/UtilityPanel/CellDetailPanel.tsx": 1, "components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx": 2, - "components/interfaces/SQLEditor/UtilityPanel/Results.tsx": 6, "components/interfaces/SQLEditor/UtilityPanel/UtilityPanel.tsx": 1, "components/interfaces/SQLEditor/hooks.ts": 1, "components/interfaces/SQLEditor/useAddDefinitions.ts": 1, "components/interfaces/SQLEditor/useSqlEditorAi.ts": 3, - "components/interfaces/SQLEditor/useSqlEditorExecution.ts": 1, "components/interfaces/Settings/Database/NetworkRestrictions/AddRestrictionModal.tsx": 1, "components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.utils.ts": 2, "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx": 1, @@ -438,11 +408,10 @@ "components/interfaces/Settings/Logs/Logs.constants.ts": 1, "components/interfaces/Settings/Logs/Logs.types.ts": 1, "components/interfaces/Settings/Logs/Logs.utils.otel.ts": 1, - "components/interfaces/Settings/Logs/Logs.utils.ts": 12, + "components/interfaces/Settings/Logs/Logs.utils.ts": 11, "components/interfaces/Settings/Logs/PreviewFilterPanel.tsx": 1, "components/interfaces/SignIn/SignInForm.tsx": 1, "components/interfaces/SignIn/SignInWithCustom.tsx": 1, - "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/CreateTable/CreateTableSheet.tsx": 2, "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/InitializeForeignSchemaDialog.tsx": 1, "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/NamespaceWithTables/TableRowComponent.tsx": 4, "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/NamespaceWithTables/index.tsx": 1, @@ -454,12 +423,11 @@ "components/interfaces/Storage/ImportForeignSchemaDialog.tsx": 1, "components/interfaces/Storage/Storage.utils.ts": 4, "components/interfaces/Storage/StorageBucketsError.tsx": 1, - "components/interfaces/Storage/StorageExplorer/FileExplorer.tsx": 2, + "components/interfaces/Storage/StorageExplorer/FileExplorer.tsx": 1, "components/interfaces/Storage/StorageExplorer/FileExplorerColumn.tsx": 5, "components/interfaces/Storage/StorageExplorer/FileExplorerRowEditing.tsx": 2, - "components/interfaces/Storage/StorageExplorer/MoveItemsModal.tsx": 1, - "components/interfaces/Storage/StorageExplorer/StorageExplorer.tsx": 1, "components/interfaces/Storage/StorageExplorer/StorageExplorer.utils.tsx": 2, + "components/interfaces/Storage/StorageExplorer/StorageExplorerContent.tsx": 1, "components/interfaces/Storage/StoragePolicies/PolicyEditor/PolicyRoles.tsx": 1, "components/interfaces/Storage/StoragePolicies/PolicyEditor/index.tsx": 2, "components/interfaces/Storage/StoragePolicies/StoragePolicies.tsx": 5, @@ -474,7 +442,6 @@ "components/interfaces/Support/AttachmentUpload.tsx": 3, "components/interfaces/Support/LinkSupportTicketForm.tsx": 6, "components/interfaces/TableGridEditor/SidePanelEditor/ActionBar.tsx": 1, - "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnDefaultValue.tsx": 2, "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx": 2, "components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx": 1, @@ -503,14 +470,10 @@ "components/layouts/Navigation/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx": 3, "components/layouts/ProjectLayout/BuildingState.tsx": 1, "components/layouts/ProjectLayout/LayoutHeader/MobileMenuContent/mobileOrgMenuRegistry.tsx": 2, - "components/layouts/SQLEditorLayout/SQLEditorMenu.tsx": 2, - "components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx": 1, "components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.utils.ts": 2, "components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx": 2, - "components/layouts/SQLEditorLayout/SQLEditorNavV2/SearchList.tsx": 1, "components/layouts/TableEditorLayout/EntityListItem.tsx": 1, "components/ui/AIAssistantPanel/AIAssistant.utils.ts": 1, - "components/ui/AIAssistantPanel/DisplayBlockRenderer.tsx": 1, "components/ui/AIAssistantPanel/MessageMarkdown.tsx": 1, "components/ui/AIEditor/index.tsx": 2, "components/ui/CardButton.tsx": 2, @@ -524,13 +487,11 @@ "components/ui/Charts/StackedBarChart.tsx": 3, "components/ui/CodeEditor/CodeEditor.tsx": 2, "components/ui/CodeEditor/Providers/BackwardIterator.ts": 1, - "components/ui/CodeEditor/Providers/PgSQLCompletionProvider.ts": 21, - "components/ui/CodeEditor/Providers/PgSQLSignatureHelpProvider.ts": 4, "components/ui/DataTable/DataTable.utils.ts": 8, - "components/ui/DataTable/DataTableInfinite.tsx": 5, + "components/ui/DataTable/DataTableInfinite.tsx": 3, "components/ui/DataTable/DataTableViewOptions.tsx": 1, "components/ui/DataTable/LiveButton.tsx": 1, - "components/ui/DataTable/providers/DataTableProvider.tsx": 3, + "components/ui/DataTable/providers/DataTableProvider.tsx": 4, "components/ui/DatePicker/DatePicker.types.ts": 2, "components/ui/DatePicker/index.tsx": 2, "components/ui/DateRangePicker.tsx": 1, @@ -547,7 +508,6 @@ "data/ai/check-api-key-query.ts": 1, "data/ai/rate-message-mutation.ts": 1, "data/ai/sql-cron-mutation.ts": 1, - "data/ai/sql-policy-mutation.ts": 1, "data/ai/sql-title-mutation.ts": 1, "data/analytics/functions-combined-stats-query.ts": 1, "data/analytics/infra-monitoring-query.ts": 1, @@ -562,7 +522,6 @@ "data/database-triggers/database-triggers-query.ts": 1, "data/database/table-columns-query.ts": 1, "data/docs/project-json-schema-query.ts": 1, - "data/documents/dpa-request-mutation.ts": 1, "data/edge-functions/edge-function-test-mutation.ts": 1, "data/edge-functions/edge-functions-deploy-mutation.ts": 2, "data/entitlements/entitlements-query.ts": 1, @@ -577,17 +536,15 @@ "data/jwt-signing-keys/jwt-signing-key-create-mutation.ts": 1, "data/log-drains/create-log-drain-mutation.ts": 1, "data/log-drains/update-log-drain-mutation.ts": 1, - "data/logs/get-unified-logs.ts": 1, "data/logs/otel-inspection.utils.ts": 2, "data/logs/unified-log-inspection-query.ts": 2, "data/logs/unified-logs-chart-query.ts": 1, "data/logs/unified-logs-count-query.ts": 1, - "data/logs/unified-logs-infinite-query.ts": 2, + "data/logs/unified-logs-infinite-query.ts": 1, "data/network-restrictions/network-restrictions-query.ts": 3, "data/notifications/keys.ts": 1, "data/oauth-secrets/client-secret-create-mutation.ts": 2, "data/open-api/api-spec-query.ts": 8, - "data/organization-members/organization-roles-query.ts": 1, "data/organizations/organization-create-mutation.ts": 2, "data/organizations/organization-customer-profile-update-mutation.ts": 1, "data/organizations/organization-payment-method-default-mutation.ts": 2, @@ -595,8 +552,7 @@ "data/profile/profile-identities-query.ts": 1, "data/profile/profile-unlink-identity-mutation.ts": 2, "data/projects/project-create-mutation.ts": 1, - "data/replication/rollback-tables-mutation.ts": 1, - "data/reports/v2/auth.config.ts": 15, + "data/reports/v2/auth.config.ts": 13, "data/reports/v2/edge-functions.config.ts": 9, "data/reports/v2/reports.types.ts": 2, "data/sql/execute-sql-mutation.ts": 5, @@ -639,7 +595,6 @@ "lib/pg-format.ts": 6, "lib/profile.tsx": 1, "lib/role-impersonation.ts": 1, - "lib/sentry-client-options.ts": 2, "lib/telemetry/track.ts": 1, "pages/_app.tsx": 1, "pages/api/ai/feedback/rate.ts": 2, @@ -655,24 +610,20 @@ "pages/project/[ref]/functions/[functionSlug]/index.tsx": 3, "pages/project/[ref]/settings/log-drains.tsx": 1, "state/ai-assistant-state.tsx": 4, - "state/role-impersonation-state.tsx": 1, "state/sql-editor/sql-editor-session-state.ts": 4, "state/storage-explorer.tsx": 15, "state/table-editor-table.tsx": 17, "state/table-editor.tsx": 5, - "tests/lib/custom-render.tsx": 1, "tests/setup/polyfills.ts": 3, "tests/vitestSetup.ts": 1, "types/next.ts": 2, "types/ui.ts": 2 }, - "no-restricted-imports": {}, "no-restricted-exports": { "__mocks__/hooks/analytics/useFillTimeseriesSorted.ts": 1, "__mocks__/hooks/analytics/useLogsQuery.ts": 1, "components/grid/components/header/filter/FilterRow.tsx": 1, "components/grid/components/header/sort/SortRow.tsx": 1, - "components/interfaces/Account/TOTPFactors/DeleteFactorModal.tsx": 1, "components/interfaces/App/CommandMenu/CommandMenu.tsx": 1, "components/interfaces/Auth/AuthProvidersForm/FormField.tsx": 1, "components/interfaces/Auth/SiteUrl/SiteUrl.tsx": 1, @@ -694,7 +645,6 @@ "components/interfaces/Database/EnumeratedTypes/CreateEnumeratedTypeSidePanel.tsx": 1, "components/interfaces/Database/EnumeratedTypes/EditEnumeratedTypeSidePanel.tsx": 1, "components/interfaces/Database/EnumeratedTypes/EnumeratedTypeValueRow.tsx": 1, - "components/interfaces/Database/Migrations/Migrations.tsx": 1, "components/interfaces/Database/Privileges/PrivilegesHead.tsx": 1, "components/interfaces/Database/Privileges/PrivilegesTable.tsx": 1, "components/interfaces/Database/Triggers/ChooseFunctionForm.tsx": 1, @@ -713,7 +663,6 @@ "components/interfaces/Integrations/CronJobs/CronJobsEmptyState.tsx": 1, "components/interfaces/Integrations/CronJobs/CronSyntaxChart.tsx": 1, "components/interfaces/Integrations/Integration/LegacyIntegrationPage.tsx": 1, - "components/interfaces/Integrations/Vercel/OrganizationPicker.tsx": 1, "components/interfaces/Integrations/Wrappers/InputField.tsx": 1, "components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx": 1, "components/interfaces/Linter/LintPageTabs.tsx": 1, @@ -729,7 +678,6 @@ "components/interfaces/Organization/BillingSettings/Subscription/UpgradeModal.tsx": 1, "components/interfaces/Organization/CloudMarketplace/AwsMarketplaceAutoRenewalWarning.tsx": 1, "components/interfaces/Organization/InvoicesSettings/InvoicePayButton.tsx": 1, - "components/interfaces/Organization/TeamSettings/MembersView.tsx": 1, "components/interfaces/Organization/Usage/ActiveCompute.tsx": 1, "components/interfaces/Organization/Usage/Activity.tsx": 1, "components/interfaces/Organization/Usage/Compute.tsx": 1, @@ -750,11 +698,9 @@ "components/interfaces/Realtime/Inspector/RealtimeFilterPopover/TableSelector.tsx": 1, "components/interfaces/Reports/ReportFilterBar.tsx": 1, "components/interfaces/Reports/ReportHeader.tsx": 1, - "components/interfaces/Reports/ReportPadding.tsx": 1, "components/interfaces/Reports/ReportStickyNav.tsx": 1, "components/interfaces/Reports/ReportWidget.tsx": 1, "components/interfaces/Reports/Reports.tsx": 1, - "components/ui/AIEditor/InlineWidget.tsx": 1, "components/interfaces/SQLEditor/SQLTemplates/SQLExamples.tsx": 1, "components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx": 1, "components/interfaces/SQLEditor/UtilityPanel/ReadOnlyBadge.tsx": 1, @@ -768,7 +714,6 @@ "components/interfaces/Settings/Database/NetworkRestrictions/AllowAllModal.tsx": 1, "components/interfaces/Settings/Database/NetworkRestrictions/DisallowAllModal.tsx": 1, "components/interfaces/Settings/Database/NetworkRestrictions/RemoveRestrictionModal.tsx": 1, - "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropAllReplicasConfirmationModal.tsx": 1, "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx": 1, "components/interfaces/Settings/Integrations/VercelIntegration/VercelIntegrationConnectionForm.tsx": 1, "components/interfaces/Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx": 1, @@ -783,7 +728,6 @@ "components/interfaces/Settings/Logs/Logs.SqlSnippetCode.tsx": 1, "components/interfaces/Settings/Logs/LogsErrorRenderers/ResourcesExceededErrorRenderer.tsx": 1, "components/interfaces/Settings/Logs/LogsFilterPopover.tsx": 1, - "components/interfaces/Settings/Logs/PreviewFilterPanel.tsx": 1, "components/interfaces/Settings/Logs/RecentQueriesItem.tsx": 1, "components/interfaces/Settings/Logs/UpgradePrompt.tsx": 1, "components/interfaces/SignIn/PasswordConditionsHelper.tsx": 1, @@ -795,7 +739,6 @@ "components/interfaces/Storage/StoragePolicies/PolicyTemplates/TemplatesList.tsx": 1, "components/interfaces/Storage/StoragePolicies/StoragePoliciesPlaceholder.tsx": 1, "components/interfaces/TableGridEditor/DeleteConfirmationDialogs.tsx": 1, - "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnDefaultValue.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx": 1, @@ -811,13 +754,10 @@ "components/layouts/AdvisorsLayout/AdvisorsLayout.tsx": 1, "components/layouts/AuthLayout/AuthLayout.tsx": 1, "components/layouts/BranchLayout/BranchLayout.tsx": 1, - "components/layouts/DatabaseLayout/DatabaseLayout.tsx": 1, "components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx": 1, "components/layouts/EdgeFunctionsLayout/EdgeFunctionsLayout.tsx": 1, "components/layouts/JWTKeys/JWTKeysLayout.tsx": 1, "components/layouts/LogsLayout/LogsLayout.tsx": 1, - "components/layouts/Navigation/NavigationBar/MobileNavigationBar.tsx": 1, - "components/layouts/Navigation/ProductMenuBar.tsx": 1, "components/layouts/ObservabilityLayout/ObservabilityLayout.tsx": 1, "components/layouts/OrganizationLayout.tsx": 1, "components/layouts/ProjectLayout/BuildingState.tsx": 1, @@ -830,15 +770,12 @@ "components/layouts/WizardLayout.tsx": 1, "components/to-be-cleaned/ProductEmptyState.tsx": 1, "components/to-be-cleaned/Table.tsx": 1, - "components/ui/AIEditor/ResizableAIWidget.tsx": 1, "components/ui/CardButton.tsx": 1, "components/ui/Charts/AreaChart.tsx": 1, "components/ui/Charts/BarChart.tsx": 1, "components/ui/Charts/NoDataPlaceholder.tsx": 1, "components/ui/Charts/StackedBarChart.tsx": 1, "components/ui/CodeEditor/Providers/BackwardIterator.ts": 1, - "components/ui/CodeEditor/Providers/PgSQLCompletionProvider.ts": 1, - "components/ui/CodeEditor/Providers/PgSQLSignatureHelpProvider.ts": 1, "components/ui/CopyButton.tsx": 1, "components/ui/CountdownTimer/CountdownTimerRadial.tsx": 1, "components/ui/CountdownTimer/CountdownTimerSpan.tsx": 1, @@ -854,15 +791,14 @@ "data/prefetchers/PrefetchableLink.tsx": 1, "hooks/analytics/useDbQuery.tsx": 1, "hooks/analytics/useLogsPreview.tsx": 1, - "hooks/analytics/useProjectUsageStats.tsx": 1, "hooks/analytics/useSingleLog.tsx": 1, "hooks/analytics/useTimeseriesUnixToIso.ts": 1, "lib/pingPostgrest.ts": 1, + "pages/_error.jsx": 1, "scripts/codegen.ts": 1 }, "react/no-unstable-nested-components": { "components/grid/components/header/filter/FilterPopoverNew.tsx": 1, - "components/interfaces/Account/Preferences/ThemeSettings.tsx": 1, "components/interfaces/App/CommandMenu/ContextSearchResults.shared.tsx": 1, "components/interfaces/ConnectSheet/ConnectStepsSection.tsx": 1, "components/interfaces/Database/Migrations/MigrationsEmptyState.tsx": 3, @@ -876,34 +812,17 @@ "components/interfaces/Settings/Logs/LogsFormatters.tsx": 2, "components/layouts/Navigation/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx": 1, "components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx": 3, - "components/layouts/SQLEditorLayout/SQLEditorNavV2/SearchList.tsx": 1, + "components/layouts/SQLEditorLayout/SQLEditorNavV2/SqlSnippetTree.tsx": 1, "components/ui/AIAssistantPanel/MessageMarkdown.tsx": 1, "components/ui/CardButton.tsx": 1, "components/ui/Charts/AreaChart.tsx": 1, "components/ui/Charts/BarChart.tsx": 1, "components/ui/Charts/ComposedChart.tsx": 2, "components/ui/Charts/ComposedChart.utils.tsx": 3, - "components/ui/ErrorBoundary/ErrorBoundary.tsx": 1, - "pages/project/[ref]/observability/database.tsx": 1 + "components/ui/ErrorBoundary/ErrorBoundary.tsx": 1 }, - "jsx-a11y/aria-props": {}, - "jsx-a11y/aria-proptypes": {}, - "jsx-a11y/role-supports-aria-props": {}, - "jsx-a11y/anchor-has-content": {}, "jsx-a11y/control-has-associated-label": { - "components/grid/components/editor/DateTimeEditor.tsx": 1, - "components/grid/components/editor/NumberEditor.tsx": 1, "components/grid/components/editor/TimeEditor.tsx": 1, - "components/grid/components/footer/pagination/Pagination.tsx": 3, - "components/grid/components/grid/AddColumn.tsx": 1, - "components/grid/components/header/filter/FilterRow.tsx": 1, - "components/grid/components/header/sort/SortRow.tsx": 2, - "components/interfaces/APIKeys/APIKeyRow.tsx": 1, - "components/interfaces/Account/AccessTokens/AccessTokenList.tsx": 1, - "components/interfaces/Account/AccessTokens/AccessTokenNewBanner/AccessTokenNewBanner.tsx": 1, - "components/interfaces/Account/AccessTokens/Classic/NewTokenButton.tsx": 1, - "components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceOption.tsx": 1, - "components/interfaces/Account/AccessTokens/Scoped/ScopedTokenList.tsx": 1, "components/interfaces/Account/Preferences/AnalyticsSettings.tsx": 1, "components/interfaces/Account/Preferences/DashboardToggle.tsx": 1, "components/interfaces/Account/Preferences/HotkeyToggle.tsx": 1, @@ -921,57 +840,35 @@ "components/interfaces/Auth/OAuthApps/OAuthAppsList.tsx": 2, "components/interfaces/Auth/OAuthApps/OAuthServerSettingsForm.tsx": 2, "components/interfaces/Auth/Passkeys/PasskeysSettingsForm.tsx": 1, - "components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx": 1, "components/interfaces/Auth/RateLimits/RateLimits.tsx": 1, "components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx": 2, - "components/interfaces/Auth/SmtpForm/SmtpForm.tsx": 1, "components/interfaces/Auth/Users/UserPanel.tsx": 1, "components/interfaces/Auth/Users/UsersFooter.tsx": 1, "components/interfaces/Auth/Users/UsersGridComponents.tsx": 1, "components/interfaces/Auth/Users/UsersSearch.tsx": 1, - "components/interfaces/BranchManagement/CreateBranchModal.tsx": 1, "components/interfaces/BranchManagement/Overview.tsx": 1, "components/interfaces/BranchManagement/WorkflowLogsCard.tsx": 1, "components/interfaces/Branching/MergeRequest.tsx": 1, "components/interfaces/ConnectSheet/ConnectConfigSection.tsx": 1, - "components/interfaces/Database/EnumeratedTypes/EnumeratedTypeValueRow.tsx": 1, - "components/interfaces/Database/Functions/CreateFunction/FunctionEditor.tsx": 1, - "components/interfaces/Database/Functions/CreateFunction/index.tsx": 2, - "components/interfaces/Database/Hooks/HooksList/HookList.tsx": 1, + "components/interfaces/Database/Functions/CreateFunction/index.tsx": 1, "components/interfaces/Database/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx": 1, - "components/interfaces/Database/Privileges/PrivilegesTable.tsx": 2, + "components/interfaces/Database/Privileges/PrivilegesTable.tsx": 1, "components/interfaces/Database/Publications/PublicationSkeleton.tsx": 1, - "components/interfaces/Database/Publications/PublicationsList.tsx": 1, "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/Fields.tsx": 2, + "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/BigQuery/Fields.tsx": 1, "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DuckLake/Fields.tsx": 2, - "components/interfaces/Database/Replication/ReadReplicas/ReadReplicaRow.tsx": 1, - "components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.tsx": 1, - "components/interfaces/Database/Replication/RowMenu.tsx": 1, "components/interfaces/Database/Roles/CreateRolePanel.tsx": 2, "components/interfaces/Database/Roles/RoleRow.tsx": 1, - "components/interfaces/Database/Tables/ColumnList.tsx": 1, "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx": 1, - "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx": 1, "components/interfaces/Functions/EdgeFunctionSecrets/EditSecretSheet.tsx": 1, "components/interfaces/Integrations/CronJobs/CreateCronJobSheet/CronJobScheduleSection.tsx": 1, - "components/interfaces/Integrations/CronJobs/CronJobsTab.Header.tsx": 1, "components/interfaces/Integrations/Queues/CreateQueueSheet/RlsSection.tsx": 1, "components/interfaces/Integrations/Queues/QueuesSettings.tsx": 1, - "components/interfaces/Integrations/Queues/QueuesTab.tsx": 1, - "components/interfaces/Integrations/Queues/SingleQueue/MessageDetailsPanel.tsx": 1, "components/interfaces/Integrations/Queues/SingleQueue/QueueSettings.tsx": 1, - "components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx": 2, - "components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx": 1, - "components/interfaces/Integrations/Wrappers/CreateWrapperSheet.tsx": 2, - "components/interfaces/Integrations/Wrappers/EditWrapperSheet.tsx": 2, - "components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx": 1, "components/interfaces/Linter/LintDetail.tsx": 1, - "components/interfaces/Linter/LinterDataGrid.tsx": 1, "components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx": 3, "components/interfaces/LogDrains/LogDrainsCard.tsx": 1, "components/interfaces/LogDrains/LogDrainsList.tsx": 1, - "components/interfaces/LogDrains/OrgAuditLogDrains.tsx": 1, - "components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx": 1, "components/interfaces/Organization/BillingSettings/BillingBreakdown/ComputeMetric.tsx": 1, "components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerDataForm.tsx": 1, "components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx": 1, @@ -979,7 +876,6 @@ "components/interfaces/Organization/NewOrg/NewOrgForm.tsx": 1, "components/interfaces/Organization/OAuthApps/AuthorizedAppRow.tsx": 1, "components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx": 1, - "components/interfaces/Organization/OAuthApps/OAuthApps.tsx": 1, "components/interfaces/Organization/OAuthApps/PublishAppSidePanel/index.tsx": 1, "components/interfaces/Organization/PrivateApps/Apps/AppsList/AppsListTable.tsx": 1, "components/interfaces/Organization/PrivateApps/Apps/CreateAppSheet/CreateAppSheet.tsx": 3, @@ -990,25 +886,19 @@ "components/interfaces/Organization/SSO/SSOMetadata.tsx": 1, "components/interfaces/Organization/SecuritySettings.tsx": 1, "components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx": 1, - "components/interfaces/Organization/TeamSettings/MemberActions.tsx": 1, "components/interfaces/Organization/TeamSettings/UpdateRolesPanel/UpdateRolesPanel.tsx": 1, "components/interfaces/Platform/Webhooks/PlatformWebhooksEndpointList.tsx": 1, "components/interfaces/Platform/Webhooks/PlatformWebhooksEndpointSheet.tsx": 1, "components/interfaces/Platform/Webhooks/PlatformWebhooksPage.tsx": 1, "components/interfaces/ProjectAPIDocs/SecondLevelNav.Layout.tsx": 1, - "components/interfaces/ProjectCreation/HighAvailabilityInput.tsx": 1, "components/interfaces/QueryInsights/QueryInsightsTable/QueryInsightsTable.tsx": 1, - "components/interfaces/QueryInsights/hooks/useQueryInsightsTableColumns.tsx": 2, + "components/interfaces/QueryInsights/hooks/useQueryInsightsTableColumns.tsx": 1, "components/interfaces/QueryPerformance/QueryPerformanceGrid.tsx": 1, "components/interfaces/QueryPerformance/components/FilterInput.tsx": 1, "components/interfaces/Realtime/Inspector/ChooseChannelPopover.tsx": 1, "components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx": 3, - "components/interfaces/Realtime/RealtimeSettings.tsx": 2, "components/interfaces/Reports/ReportFilterBar.tsx": 1, "components/interfaces/Reports/ReportWidget.tsx": 1, - "components/interfaces/RoleImpersonationSelector/RoleImpersonationRadio.tsx": 1, - "components/interfaces/RoleImpersonationSelector/UserImpersonationSelector.tsx": 1, - "components/interfaces/SQLEditor/OngoingQueriesPanel.tsx": 2, "components/interfaces/Settings/API/DataApiEnableSwitchForm.tsx": 1, "components/interfaces/Settings/API/ExposedFunctionSelector.tsx": 1, "components/interfaces/Settings/API/ExposedSchemaSelector.tsx": 1, @@ -1018,17 +908,14 @@ "components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx": 1, "components/interfaces/Settings/Database/SSLConfiguration.tsx": 1, "components/interfaces/Settings/General/ComplianceConfig/ProjectComplianceMode.tsx": 1, - "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx": 3, + "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx": 2, "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx": 2, "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx": 1, - "components/interfaces/Settings/Integrations/AWSPrivateLink/AWSPrivateLinkAccountItem.tsx": 1, + "components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicaRow.tsx": 1, "components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx": 3, "components/interfaces/Settings/Integrations/VercelIntegration/VercelIntegrationConnectionForm.tsx": 3, "components/interfaces/Settings/Logs/MultiSelectActionBar.tsx": 1, - "components/interfaces/Settings/Logs/PreviewFilterPanel.tsx": 1, "components/interfaces/SignIn/ResetPasswordForm.tsx": 2, - "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/CreateTable/CreateTableInstructionsDialog.tsx": 1, - "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/CreateTable/CreateTableSheet.tsx": 1, "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/DecryptedReadOnlyInput.tsx": 2, "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/NamespaceWithTables/TableRowComponent.tsx": 3, "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/NamespaceWithTables/index.tsx": 1, @@ -1038,61 +925,47 @@ "components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx": 2, "components/interfaces/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx": 1, "components/interfaces/Storage/StorageExplorer/FileExplorerRowEditing.tsx": 2, - "components/interfaces/Storage/StorageExplorer/MoveItemsModal.tsx": 1, "components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketsSection.tsx": 1, "components/interfaces/Storage/StorageSettings/S3Connection.tsx": 1, "components/interfaces/Storage/StorageSettings/StorageCredItem.tsx": 1, "components/interfaces/Storage/StorageSettings/StorageSettings.tsx": 1, "components/interfaces/Storage/VectorBuckets/CreateVectorTableSheet.tsx": 1, - "components/interfaces/Storage/VectorBuckets/VectorBucketDetails/index.tsx": 1, "components/interfaces/Support/AttachmentUpload.tsx": 1, "components/interfaces/Support/DashboardLogsToggle.tsx": 1, "components/interfaces/Support/SupportAccessToggle.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx": 1, - "components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/Pagination.tsx": 2, "components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ApiAccessToggle.tsx": 1, - "components/interfaces/UnifiedLogs/ServiceFlow/components/ServiceFlowPanelControls.tsx": 3, - "components/interfaces/UnifiedLogs/ServiceFlow/components/shared/DetailRow.tsx": 1, "components/layouts/Navigation/FloatingMobileToolbar/FloatingMobileToolbar.tsx": 2, "components/layouts/Navigation/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx": 2, - "components/layouts/Navigation/NavigationBar/MobileNavigationBar.tsx": 1, "components/layouts/ProjectLayout/PauseFailedState.tsx": 1, "components/layouts/ProjectLayout/RestoreFailedState.tsx": 1, - "components/layouts/TableEditorLayout/EntityListItem.tsx": 1, "components/ui/AIAssistantPanel/CollapsibleCodeBlock.tsx": 1, "components/ui/AdvisorPanel/AdvisorPanelBody.tsx": 1, "components/ui/AdvisorPanel/AdvisorSignalDetail.tsx": 1, - "components/ui/AiAssistantDropdown.tsx": 1, "components/ui/Charts/ReportSettings.tsx": 2, - "components/ui/EditorPanel/EditorPanel.tsx": 1, "components/ui/Logs/LogsExplorerHeader.tsx": 1, "components/ui/PasswordStrengthBar.tsx": 1, "components/ui/Resource/ResourceItem.tsx": 1, "pages/project/[ref]/auth/templates/[templateId].tsx": 1, "pages/project/[ref]/branches/merge-requests.tsx": 1, - "pages/project/[ref]/functions/index.tsx": 1, "pages/project/[ref]/observability/auth.tsx": 1, - "pages/project/[ref]/observability/database.tsx": 1, "pages/project/[ref]/observability/edge-functions.tsx": 1, "pages/project/[ref]/observability/postgrest.tsx": 1, "pages/project/[ref]/observability/realtime.tsx": 1, - "pages/project/[ref]/observability/storage.tsx": 1, - "pages/project/[ref]/settings/log-drains.tsx": 1 + "pages/project/[ref]/observability/storage.tsx": 1 }, "jsx-a11y/label-has-associated-control": { - "components/grid/components/header/sort/SortRow.tsx": 1, "components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx": 1, "components/interfaces/Auth/RedirectUrls/AddNewURLModal.tsx": 1, - "components/interfaces/BranchManagement/CreateBranchModal.tsx": 2, - "components/interfaces/BranchManagement/EditBranchModal.tsx": 1, "components/interfaces/Docs/Param.tsx": 3, "components/interfaces/Docs/ResourceContent.tsx": 1, "components/interfaces/Docs/RpcContent.tsx": 1, + "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx": 2, "components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx": 1, "components/interfaces/Organization/PrivateApps/Apps/CreateAppSheet/CreateAppSheet.tsx": 1, "components/interfaces/Platform/Webhooks/PlatformWebhooksEndpointSheet.tsx": 1, @@ -1107,83 +980,26 @@ "components/interfaces/Storage/StorageExplorer/PreviewPane.tsx": 2, "components/interfaces/Storage/StoragePolicies/PolicyTemplates/TemplatePreview.tsx": 1, "components/interfaces/Storage/VectorBuckets/CreateVectorTableSheet.tsx": 1, - "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnDefaultValue.tsx": 1, "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx": 1, "components/ui/Charts/ComposedChart.tsx": 1, "components/ui/Charts/ComposedChart.utils.tsx": 2 }, - "jsx-a11y/aria-role": {}, "jsx-a11y/no-redundant-roles": { "components/interfaces/Organization/BillingSettings/Subscription/EnterpriseCard.tsx": 1, - "components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx": 1, - "components/interfaces/RoleImpersonationSelector/UserImpersonationSelector.tsx": 2 + "components/interfaces/Organization/BillingSettings/Subscription/PlanCard.tsx": 1 }, - "jsx-a11y/no-aria-hidden-on-focusable": {}, - "jsx-a11y/tabindex-no-positive": {}, "jsx-a11y/anchor-is-valid": { "components/interfaces/BranchManagement/Overview.tsx": 1, "components/interfaces/Organization/ProjectClaim/confirm.tsx": 1, "components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx": 2, - "components/interfaces/SignIn/SignInMfaForm.tsx": 1, "components/interfaces/TableGridEditor/GridHeaderActions.tsx": 1, "components/layouts/Navigation/LayoutHeader/BreadcrumbsView.tsx": 1 }, "jsx-a11y/heading-has-content": { "components/layouts/Scaffold.tsx": 2 }, - "jsx-a11y/no-distracting-elements": {}, "valtio/state-snapshot-rule": { "state/storage-explorer.tsx": 1 - }, - "react-hook-form/no-use-watch": { - "components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.tsx": 3, - "components/interfaces/Account/Preferences/DeleteAccountButton.tsx": 1, - "components/interfaces/Advisors/CreateRuleSheet.tsx": 1, - "components/interfaces/Auth/AuditLogsForm.tsx": 1, - "components/interfaces/Auth/BasicAuthSettingsForm.tsx": 2, - "components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx": 1, - "components/interfaces/Auth/Hooks/CreateHookSheet.tsx": 1, - "components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx": 2, - "components/interfaces/Auth/OAuthApps/CreateOrUpdateOAuthAppSheet.tsx": 1, - "components/interfaces/Auth/OAuthApps/OAuthServerSettingsForm.tsx": 3, - "components/interfaces/Auth/PerformanceSettingsForm.tsx": 1, - "components/interfaces/Auth/RateLimits/RateLimits.tsx": 6, - "components/interfaces/Auth/RedirectUrls/AddNewURLModal.tsx": 1, - "components/interfaces/Auth/SmtpForm/SmtpForm.tsx": 2, - "components/interfaces/Auth/ThirdPartyAuthForm/CreateAwsCognitoAuthDialog.tsx": 1, - "components/interfaces/Auth/Users/BanUserModal.tsx": 1, - "components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx": 1, - "components/interfaces/BranchManagement/CreateBranchModal.tsx": 1, - "components/interfaces/Database/Extensions/EnableExtensionModal.tsx": 1, - "components/interfaces/Database/Functions/CreateFunction/index.tsx": 1, - "components/interfaces/Database/Policies/PolicyEditorPanel/index.tsx": 1, - "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx": 1, - "components/interfaces/Database/Triggers/TriggerSheet.tsx": 1, - "components/interfaces/DiskManagement/DiskManagementForm.tsx": 3, - "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx": 1, - "components/interfaces/Integrations/Queues/QueuesSettings.tsx": 1, - "components/interfaces/Integrations/Wrappers/CreateIcebergWrapperSheet.tsx": 1, - "components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx": 2, - "components/interfaces/Organization/BillingSettings/BillingEmail.tsx": 1, - "components/interfaces/Organization/BillingSettings/CreditTopUp.tsx": 1, - "components/interfaces/Organization/CloudMarketplace/NewAwsMarketplaceOrgForm.tsx": 1, - "components/interfaces/Organization/NewOrg/NewOrgForm.tsx": 5, - "components/interfaces/Organization/SSO/SSOConfig.tsx": 4, - "components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx": 1, - "components/interfaces/Platform/Webhooks/PlatformWebhooksEndpointSheet.tsx": 2, - "components/interfaces/Realtime/RealtimeSettings.tsx": 1, - "components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx": 1, - "components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessRuleSheet.tsx": 1, - "components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsConfigureHostname.tsx": 1, - "components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx": 3, - "components/interfaces/SignIn/ResetPasswordForm.tsx": 1, - "components/interfaces/SignIn/SignInMfaForm.tsx": 1, - "components/interfaces/SignIn/SignUpForm.tsx": 1, - "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/CreateTable/CreateTableSheet.tsx": 2, - "components/interfaces/Storage/CreateBucketModal.tsx": 2, - "components/interfaces/Storage/EditBucketModal.tsx": 2, - "components/interfaces/Storage/StorageSettings/StorageSettings.tsx": 1, - "components/interfaces/Support/LinkSupportTicketForm.tsx": 1 } } } diff --git a/apps/studio/TANSTACK_MIGRATION.md b/apps/studio/TANSTACK_MIGRATION.md index 1ef32b7e503f5..b07f589a098fa 100644 --- a/apps/studio/TANSTACK_MIGRATION.md +++ b/apps/studio/TANSTACK_MIGRATION.md @@ -442,8 +442,9 @@ per-file items only when a subtree has special cases. - [x] `routes/api/v1/**` — except `body.ts` (streaming rewrite) - [x] `routes/api/v1/projects/$ref/functions/$slug/body.ts` — Web-streams rewrite. Returns a `Response` whose body is a `ReadableStream`; each artifact file is converted via `Readable.toWeb(createReadStream(...))` and pulled chunk-by-chunk into the multipart stream. Skips the `apiWrapper` since `getFunctionsArtifactStore` already asserts self-hosted mode (the pages-router `withAuth` was a no-op outside `IS_PLATFORM`). - [x] `routes/api/mcp/index.ts` — uses `WebStandardStreamableHTTPServerTransport` from `@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js`. Takes a Web `Request`, returns a `Response` directly — no shim needed. Query parsing pulled from `request.url`'s search params; headers passed through unchanged. -- [x] `routes/api/incident-banner.ts`, `routes/api/incident-status.ts` — App - Router routes under `app/api/**` (already Web-native, direct re-export) +- [x] `routes/api/incident-banner.ts`, `routes/api/incident-status.ts`, + `routes/api/status-page.ts` — App Router routes under `app/api/**` + (already Web-native, direct re-export) --- diff --git a/apps/studio/app/api/status-page/route.ts b/apps/studio/app/api/status-page/route.ts new file mode 100644 index 0000000000000..6607ffaddea15 --- /dev/null +++ b/apps/studio/app/api/status-page/route.ts @@ -0,0 +1,65 @@ +import { IS_PLATFORM } from 'common' +import { NextResponse } from 'next/server' + +import { InternalServerError } from '@/lib/api/apiHelpers' +import { getStatusPage } from '@/lib/api/status-page' + +const CACHE_CONTROL_SUCCESS = 'public, s-maxage=300, stale-while-revalidate=300' +const CACHE_CONTROL_DEGRADED = 'public, s-maxage=60, stale-while-revalidate=60' +const CACHE_CONTROL_ERROR = 'no-store' + +export async function OPTIONS() { + if (!IS_PLATFORM) return new Response(null, { status: 404 }) + return new Response(null, { + status: 204, + headers: { Allow: 'GET, HEAD, OPTIONS' }, + }) +} + +export async function HEAD() { + if (!IS_PLATFORM) return new Response(null, { status: 404 }) + return new Response(null, { + status: 200, + headers: { 'Cache-Control': CACHE_CONTROL_SUCCESS }, + }) +} + +export async function GET() { + if (!IS_PLATFORM) return new Response(null, { status: 404 }) + + try { + const { data, isDegraded } = await getStatusPage() + return NextResponse.json(data, { + headers: { 'Cache-Control': isDegraded ? CACHE_CONTROL_DEGRADED : CACHE_CONTROL_SUCCESS }, + }) + } catch (error) { + let errorCode = 500 + const headers = new Headers({ 'Cache-Control': CACHE_CONTROL_ERROR }) + + if (error instanceof InternalServerError) { + if (typeof error.details?.status === 'number') errorCode = error.details.status + if (errorCode === 420) errorCode = 429 + if (errorCode === 429 && typeof error.details?.retryAfter === 'string') { + headers.set('Retry-After', error.details.retryAfter) + } + console.error( + 'Failed to fetch incident.io status page: %s', + JSON.stringify( + { + message: error.message, + details: error.details, + }, + null, + 2 + ) + ) + } else { + console.error('Unexpected error fetching incident.io status page:', error) + } + + return NextResponse.json( + { error: { message: 'Internal server error' } }, + { status: errorCode, headers } + ) + } +} diff --git a/apps/studio/components/interfaces/App/ShellFallback.tsx b/apps/studio/components/interfaces/App/ShellFallback.tsx index ff6a1666af791..24722a22998fd 100644 --- a/apps/studio/components/interfaces/App/ShellFallback.tsx +++ b/apps/studio/components/interfaces/App/ShellFallback.tsx @@ -1,5 +1,10 @@ import { LogoLoader } from 'ui' +import { + BOOT_FALLBACK_MESSAGE, + BOOT_FALLBACK_REVEAL_DELAY_SECONDS, + BOOT_FALLBACK_SUPPORT_EMAIL, +} from '@/lib/boot-fallback-copy' import { IS_PLATFORM } from '@/lib/constants' // Baked into the prerendered SPA shell (_shell.html) — this is the static HTML @@ -15,7 +20,7 @@ export function ShellFallback() { #studio-shell-help { visibility: hidden; opacity: 0; - animation: studio-shell-help-reveal 0.3s ease-out 7s forwards; + animation: studio-shell-help-reveal 0.3s ease-out ${BOOT_FALLBACK_REVEAL_DELAY_SECONDS}s forwards; } @keyframes studio-shell-help-reveal { to { @@ -40,13 +45,13 @@ export function ShellFallback() { data-nosnippet="" className="max-w-md text-center text-sm text-foreground-light" > - Taking longer than expected? Try clearing your browser cookies and reloading the page. + {BOOT_FALLBACK_MESSAGE} {IS_PLATFORM && ( <> {' '} If the problem persists, contact{' '} - - support@supabase.com + + {BOOT_FALLBACK_SUPPORT_EMAIL} . diff --git a/apps/studio/components/interfaces/LocalDropdown.test.tsx b/apps/studio/components/interfaces/LocalDropdown.test.tsx index 800a8dce2efee..df1b4dfe752ea 100644 --- a/apps/studio/components/interfaces/LocalDropdown.test.tsx +++ b/apps/studio/components/interfaces/LocalDropdown.test.tsx @@ -137,7 +137,7 @@ vi.mock('ui', async () => { checked?: boolean onCheckedChange?: (checked: boolean) => void }) => ( - ), diff --git a/apps/studio/components/ui/BootTimeoutFallback/BootTimeoutFallback.tsx b/apps/studio/components/ui/BootTimeoutFallback/BootTimeoutFallback.tsx new file mode 100644 index 0000000000000..5ac98fe7087d2 --- /dev/null +++ b/apps/studio/components/ui/BootTimeoutFallback/BootTimeoutFallback.tsx @@ -0,0 +1,75 @@ +import { + BOOT_FALLBACK_MESSAGE, + BOOT_FALLBACK_REVEAL_DELAY_SECONDS, + BOOT_FALLBACK_SUPPORT_EMAIL, +} from '@/lib/boot-fallback-copy' +import { IS_PLATFORM } from '@/lib/constants' + +const FALLBACK_ID = 'studio-boot-fallback' + +// Next.js equivalent of the TanStack build's ShellFallback (see +// components/interfaces/App/ShellFallback.tsx): a safety net for a stuck +// chunk load (FE-4460). Next always server-renders real page content into +//
, so — unlike TanStack's ClientOnly fallback, which shows nothing +// until hydration — this can't be a full-screen overlay shown by default; it +// has to reveal itself only once it's clear the page is actually stuck. +// +// The reveal is pure CSS (a delayed animation), not a JS timer, so it still +// fires even if every JS chunk fails to load. Hiding it on a successful +// mount necessarily needs JS — see `clearBootTimeoutFallback` — but a +// missed hide is harmless: the reveal only starts after +// BOOT_FALLBACK_REVEAL_DELAY_SECONDS, by which point a successful mount has +// long since hidden it. +export function BootTimeoutFallback() { + return ( + <> + +
+

+ {BOOT_FALLBACK_MESSAGE} + {IS_PLATFORM && ( + <> + {' '} + If the problem persists, contact{' '} + {BOOT_FALLBACK_SUPPORT_EMAIL}. + + )} +

+
+ + ) +} + +/** Call once the app has actually mounted, to hide the fallback above. */ +export function clearBootTimeoutFallback() { + const el = document.getElementById(FALLBACK_ID) + if (el) el.style.display = 'none' +} diff --git a/apps/studio/eslint.config.cjs b/apps/studio/eslint.config.cjs index fac0910d061ad..608d018cc2974 100644 --- a/apps/studio/eslint.config.cjs +++ b/apps/studio/eslint.config.cjs @@ -73,10 +73,10 @@ module.exports = defineConfig([ 'barrel-files/avoid-re-export-all': 'error', 'jsx-a11y/alt-text': 'warn', 'jsx-a11y/role-has-required-aria-props': 'error', - 'jsx-a11y/aria-props': 'warn', - 'jsx-a11y/aria-proptypes': 'warn', - 'jsx-a11y/role-supports-aria-props': 'warn', - 'jsx-a11y/anchor-has-content': 'warn', + 'jsx-a11y/aria-props': 'error', + 'jsx-a11y/aria-proptypes': 'error', + 'jsx-a11y/role-supports-aria-props': 'error', + 'jsx-a11y/anchor-has-content': 'error', 'jsx-a11y/control-has-associated-label': [ 'warn', { controlComponents: ['Button', 'Switch'] }, @@ -85,19 +85,19 @@ module.exports = defineConfig([ 'warn', { labelComponents: ['Label'], controlComponents: ['Input', 'Switch'] }, ], - 'jsx-a11y/aria-role': 'warn', + 'jsx-a11y/aria-role': 'error', 'jsx-a11y/no-redundant-roles': 'warn', - 'jsx-a11y/no-aria-hidden-on-focusable': 'warn', - 'jsx-a11y/tabindex-no-positive': 'warn', + 'jsx-a11y/no-aria-hidden-on-focusable': 'error', + 'jsx-a11y/tabindex-no-positive': 'error', 'jsx-a11y/anchor-is-valid': 'warn', 'jsx-a11y/heading-has-content': 'warn', - 'jsx-a11y/no-distracting-elements': 'warn', + 'jsx-a11y/no-distracting-elements': 'error', 'valtio/state-snapshot-rule': 'warn', 'valtio/avoid-this-in-proxy': 'error', 'react-hook-form/destructuring-formstate': 'error', 'react-hook-form/no-access-control': 'error', 'react-hook-form/no-nested-object-setvalue': 'error', - 'react-hook-form/no-use-watch': 'warn', + 'react-hook-form/no-use-watch': 'error', }, }, // Analytics SQL wire boundary: every call to a SQL-bearing analytics diff --git a/apps/studio/hooks/custom-content/CustomContent.types.ts b/apps/studio/hooks/custom-content/CustomContent.types.ts index f8ac5f665f6f2..6c9f15cd334f8 100644 --- a/apps/studio/hooks/custom-content/CustomContent.types.ts +++ b/apps/studio/hooks/custom-content/CustomContent.types.ts @@ -39,4 +39,6 @@ export type CustomContentTypes = { infraAwsNimbusLabel: string sslCertificateUrl: string + + statusPageVisibilityFieldIds: Array } diff --git a/apps/studio/hooks/custom-content/custom-content.json b/apps/studio/hooks/custom-content/custom-content.json index 0f094e3788cfc..b7e1439b24162 100644 --- a/apps/studio/hooks/custom-content/custom-content.json +++ b/apps/studio/hooks/custom-content/custom-content.json @@ -60,5 +60,7 @@ "infra:cloud_providers": ["AWS", "AWS_K8S", "FLY"], "infra:aws_nimbus_label": "AWS Nimbus", - "ssl:certificate_url": "https://supabase-downloads.s3-ap-southeast-1.amazonaws.com/${env}/ssl/${env}-ca-2021.crt" + "ssl:certificate_url": "https://supabase-downloads.s3-ap-southeast-1.amazonaws.com/${env}/ssl/${env}-ca-2021.crt", + + "status_page:visibility_field_ids": ["", "01M3D1YZJ1FV5495GP53VMZKCY"] } diff --git a/apps/studio/hooks/custom-content/custom-content.sample.json b/apps/studio/hooks/custom-content/custom-content.sample.json index 1fcc2e6a8101d..ca47096b02ab4 100644 --- a/apps/studio/hooks/custom-content/custom-content.sample.json +++ b/apps/studio/hooks/custom-content/custom-content.sample.json @@ -60,5 +60,7 @@ "infra:cloud_providers": ["AWS_NIMBUS"], "infra:aws_nimbus_label": "AWS Nimbus", - "ssl:certificate_url": "https://supabase-downloads.s3-ap-southeast-1.amazonaws.com/${env}/ssl/${env}-ca-2021.crt" + "ssl:certificate_url": "https://supabase-downloads.s3-ap-southeast-1.amazonaws.com/${env}/ssl/${env}-ca-2021.crt", + + "status_page:visibility_field_ids": ["01TESTOWNFIELDID000000000", "02TESTOTHERFIELDID00000000"] } diff --git a/apps/studio/hooks/custom-content/custom-content.schema.json b/apps/studio/hooks/custom-content/custom-content.schema.json index 508f26527b494..86a2a495a711a 100644 --- a/apps/studio/hooks/custom-content/custom-content.schema.json +++ b/apps/studio/hooks/custom-content/custom-content.schema.json @@ -105,6 +105,14 @@ "ssl:certificate_url": { "type": "string", "description": "The URL of the SSL certificate" + }, + + "status_page:visibility_field_ids": { + "type": "array", + "description": "Deployment-mode visibility field IDs on incident.io response incidents. Element 0 is this build's own custom-field ID (empty string = default Supabase build); the rest are other modes' field IDs.", + "items": { + "type": "string" + } } }, "required": [ diff --git a/apps/studio/lib/api/status-page.test.ts b/apps/studio/lib/api/status-page.test.ts new file mode 100644 index 0000000000000..41dec153f523d --- /dev/null +++ b/apps/studio/lib/api/status-page.test.ts @@ -0,0 +1,304 @@ +import { http, HttpResponse } from 'msw' +import { afterEach, describe, expect, test, vi } from 'vitest' + +import { mswServer } from '@/tests/lib/msw' + +vi.mock('common', async (importOriginal) => { + const actual = await importOriginal() + return { ...actual, IS_PLATFORM: true } +}) + +const WIDGET_URL = 'https://api.incident.io/widget' +const API_KEY = 'test-api-key' +const STATUS_PAGE_ID = 'test-status-page-id' + +function stubHappyEnv() { + vi.stubEnv('INCIDENT_IO_WIDGET_URL', WIDGET_URL) + vi.stubEnv('INCIDENT_IO_API_KEY', API_KEY) + vi.stubEnv('INCIDENT_IO_STATUS_PAGE_ID', STATUS_PAGE_ID) +} + +function widgetFixture() { + return { + page_title: 'Supabase Status', + page_url: 'https://status.supabase.com', + ongoing_incidents: [ + { + id: 'incident-1', + name: 'Elevated error rates', + url: 'https://status.supabase.com/incidents/incident-1', + last_update_at: '2026-01-01T00:00:00Z', + last_update_message: 'Investigating', + status: 'investigating', + current_worst_impact: 'partial_outage', + affected_components: [ + { + id: 'component-1', + name: 'API', + group_name: null, + current_status: 'partial_outage', + }, + ], + }, + ], + in_progress_maintenances: [], + scheduled_maintenances: [], + } +} + +afterEach(() => { + vi.unstubAllEnvs() + vi.useRealTimers() + vi.restoreAllMocks() +}) + +describe('getStatusPage', () => { + test('happy path: annotates the item from its linked response incident', async () => { + stubHappyEnv() + mswServer.use( + http.get(WIDGET_URL, () => HttpResponse.json(widgetFixture())), + http.get('*/v1/status-pages/:statusPageId/incidents/:itemId/response-incidents', () => + HttpResponse.json({ + incidents: [{ id: 'response-incident-1', linked_at: '2026-01-01T00:00:00Z' }], + }) + ), + http.get('*/v2/incidents/:id', () => + HttpResponse.json({ + incident: { + id: 'response-incident-1', + custom_field_entries: [], + }, + }) + ) + ) + + const { getStatusPage } = await import('./status-page') + const result = await getStatusPage() + + expect(result.isDegraded).toBe(false) + expect(result.data.ongoing_incidents[0]).toMatchObject({ + id: 'incident-1', + visible: true, + show_banner: true, + }) + }) + + test('link endpoint 404 → item gets default annotations, isDegraded stays false', async () => { + stubHappyEnv() + mswServer.use( + http.get(WIDGET_URL, () => HttpResponse.json(widgetFixture())), + http.get( + '*/v1/status-pages/:statusPageId/incidents/:itemId/response-incidents', + () => new HttpResponse(null, { status: 404 }) + ) + ) + + const { getStatusPage } = await import('./status-page') + const result = await getStatusPage() + + expect(result.data.ongoing_incidents[0]).toMatchObject({ + visible: true, + show_banner: true, + }) + expect(result.isDegraded).toBe(false) + }) + + test('/v2/incidents/:id failing → item gets default annotations AND isDegraded true', async () => { + stubHappyEnv() + mswServer.use( + http.get(WIDGET_URL, () => HttpResponse.json(widgetFixture())), + http.get('*/v1/status-pages/:statusPageId/incidents/:itemId/response-incidents', () => + HttpResponse.json({ + incidents: [{ id: 'response-incident-1', linked_at: '2026-01-01T00:00:00Z' }], + }) + ), + http.get('*/v2/incidents/:id', () => new HttpResponse(null, { status: 500 })) + ) + + const { getStatusPage } = await import('./status-page') + const result = await getStatusPage() + + expect(result.data.ongoing_incidents[0]).toMatchObject({ + visible: true, + show_banner: true, + }) + expect(result.isDegraded).toBe(true) + }) + + test('widget returns 503 → getStatusPage rejects', async () => { + stubHappyEnv() + mswServer.use(http.get(WIDGET_URL, () => new HttpResponse(null, { status: 503 }))) + + const { getStatusPage } = await import('./status-page') + await expect(getStatusPage()).rejects.toThrow() + }) + + test('missing API key / status page ID → succeeds with default annotations everywhere, isDegraded true', async () => { + vi.stubEnv('INCIDENT_IO_WIDGET_URL', WIDGET_URL) + vi.stubEnv('INCIDENT_IO_API_KEY', '') + vi.stubEnv('INCIDENT_IO_STATUS_PAGE_ID', '') + mswServer.use(http.get(WIDGET_URL, () => HttpResponse.json(widgetFixture()))) + + const { getStatusPage } = await import('./status-page') + const result = await getStatusPage() + + expect(result.isDegraded).toBe(true) + expect(result.data.ongoing_incidents[0]).toMatchObject({ + visible: true, + show_banner: true, + }) + }) + + test('real payload: scheduled maintenance without starts_at/ends_at still parses', async () => { + stubHappyEnv() + + const realPayload = { + page_title: 'Supabase', + page_url: 'https://statuspage.incident.io/supabase', + ongoing_incidents: [ + { + name: '401 errors due to JWT rejections', + status: 'identified', + url: 'https://statuspage.incident.io/supabase/incidents/01KZZ19EQMSC9DNDA15ZGVY19V', + id: '01KZZ19EQMSC9DNDA15ZGVY19V', + current_worst_impact: 'degraded_performance', + affected_components: [ + { + id: '01KJZVWMNKQ0HV8DXHRSSERV5T', + name: 'API Gateway', + current_status: 'degraded_performance', + }, + ], + last_update_at: '2026-09-25T16:23:02.23Z', + last_update_message: + 'The deployment process changes have been rolled out across all regions. We will monitor throughout the weekend.', + }, + ], + in_progress_maintenances: [], + scheduled_maintenances: [ + { + id: '01KWA5M6EAYR6F74N18REZNYQ1', + name: 'Scheduled Platform Maintenance 07/02', + status: 'maintenance_scheduled', + url: 'https://statuspage.incident.io/supabase/incidents/01KWA5M6EAYR6F74N18REZNYQ1', + affected_components: [], + last_update_at: '2026-06-29T16:56:35.249Z', + last_update_message: 'We will be carrying out a scheduled maintenance window...', + }, + ], + } + + mswServer.use( + http.get(WIDGET_URL, () => HttpResponse.json(realPayload)), + http.get( + '*/v1/status-pages/:statusPageId/incidents/:itemId/response-incidents', + () => new HttpResponse(null, { status: 404 }) + ) + ) + + const { getStatusPage } = await import('./status-page') + const result = await getStatusPage() + + expect(result.isDegraded).toBe(false) + expect(result.data.ongoing_incidents).toHaveLength(1) + expect(result.data.scheduled_maintenances).toHaveLength(1) + expect(result.data.scheduled_maintenances[0]).toMatchObject({ + id: '01KWA5M6EAYR6F74N18REZNYQ1', + visible: true, + show_banner: true, + banner_lead_days: null, + }) + expect(result.data.scheduled_maintenances[0].starts_at).toBeUndefined() + expect(result.data.scheduled_maintenances[0].ends_at).toBeUndefined() + }) +}) + +describe('fetchWithRetry backoff (via getStatusPage widget fetch)', () => { + test('a Retry-After that parses to 0 does not skip the equal-jitter backoff floor', async () => { + stubHappyEnv() + vi.useFakeTimers() + // Equal jitter is `base / 2 + random() * (base / 2)`; pinning random() to 0 collapses it to + // the deterministic floor (base / 2) so the wait boundary below is exact, not probabilistic. + vi.spyOn(Math, 'random').mockReturnValue(0) + + let widgetCallCount = 0 + mswServer.use( + http.get(WIDGET_URL, () => { + widgetCallCount++ + if (widgetCallCount === 1) { + return new HttpResponse(null, { + status: 429, + headers: { 'Retry-After': new Date(Date.now() - 10_000).toUTCString() }, + }) + } + return HttpResponse.json(widgetFixture()) + }), + http.get( + '*/v1/status-pages/:statusPageId/incidents/:itemId/response-incidents', + () => new HttpResponse(null, { status: 404 }) + ) + ) + + const { getStatusPage } = await import('./status-page') + const resultPromise = getStatusPage() + + await vi.advanceTimersByTimeAsync(0) + expect(widgetCallCount).toBe(1) + + // Backoff floor for the first retry (attempt 0) is (1000 * 2 ** 0) / 2 = 500ms with + // Math.random() pinned to 0. + await vi.advanceTimersByTimeAsync(499) + expect(widgetCallCount).toBe(1) + + // Crossing the floor triggers the retry. + await vi.advanceTimersByTimeAsync(1) + expect(widgetCallCount).toBe(2) + + const result = await resultPromise + expect(result.data.ongoing_incidents[0].id).toBe('incident-1') + }) +}) + +describe('GET /api/status-page route Cache-Control header', () => { + test('success → 300s cache with 300s stale-while-revalidate', async () => { + stubHappyEnv() + mswServer.use( + http.get(WIDGET_URL, () => HttpResponse.json(widgetFixture())), + http.get( + '*/v1/status-pages/:statusPageId/incidents/:itemId/response-incidents', + () => new HttpResponse(null, { status: 404 }) + ) + ) + + const { GET } = await import('../../app/api/status-page/route') + const response = await GET() + + expect(response.headers.get('Cache-Control')).toBe( + 'public, s-maxage=300, stale-while-revalidate=300' + ) + }) + + test('degraded (missing config) → 60s cache with 60s stale-while-revalidate', async () => { + vi.stubEnv('INCIDENT_IO_WIDGET_URL', WIDGET_URL) + vi.stubEnv('INCIDENT_IO_API_KEY', '') + vi.stubEnv('INCIDENT_IO_STATUS_PAGE_ID', '') + mswServer.use(http.get(WIDGET_URL, () => HttpResponse.json(widgetFixture()))) + + const { GET } = await import('../../app/api/status-page/route') + const response = await GET() + + expect(response.headers.get('Cache-Control')).toBe( + 'public, s-maxage=60, stale-while-revalidate=60' + ) + }) + + test('error (widget 503) → no-store', async () => { + stubHappyEnv() + mswServer.use(http.get(WIDGET_URL, () => new HttpResponse(null, { status: 503 }))) + + const { GET } = await import('../../app/api/status-page/route') + const response = await GET() + + expect(response.headers.get('Cache-Control')).toBe('no-store') + }) +}) diff --git a/apps/studio/lib/api/status-page.ts b/apps/studio/lib/api/status-page.ts new file mode 100644 index 0000000000000..5ecd394d0c6ac --- /dev/null +++ b/apps/studio/lib/api/status-page.ts @@ -0,0 +1,223 @@ +import { z } from 'zod' + +import customContentRaw from '@/hooks/custom-content/custom-content.json' +import { InternalServerError } from '@/lib/api/apiHelpers' +import { + annotateWidget, + mergeLinkedFlags, + type ResponseIncident, + type ResponseIncidentFlags, +} from '@/lib/api/status-page.utils' +import { + WidgetResponseSchema, + type StatusPageResponse, + type WidgetResponse, +} from '@/lib/status-page/status-page.schema' + +const HIDE_BANNER_FIELD_ID = '01M3D30MY0GQ1284ERHHV4DFAT' +const BANNER_LEAD_DAYS_FIELD_ID = '01M3D332BQVSDE4ZGK86X6X69V' + +const WIDGET_TIMEOUT_MS = 10_000 +const LINK_TIMEOUT_MS = 5_000 +const INCIDENT_TIMEOUT_MS = 5_000 +const MAX_RETRIES = 2 + +export type StatusPageResult = { data: StatusPageResponse; isDegraded: boolean } + +const LinkedResponseIncidentsSchema = z.object({ + incidents: z.array(z.object({ id: z.string(), linked_at: z.string() })), +}) + +const CustomFieldValueSchema = z.object({ + value_option: z.object({ value: z.string() }).nullish(), + value_text: z.string().nullish(), + value_numeric: z.string().nullish(), +}) + +const ResponseIncidentSchema = z.object({ + incident: z.object({ + id: z.string(), + custom_field_entries: z.array( + z.object({ + custom_field: z.object({ id: z.string() }), + values: z.array(CustomFieldValueSchema), + }) + ), + }), +}) + +function parseRetryAfterMs(header: string | null): number | undefined { + if (!header) return undefined + + const seconds = Number(header) + if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000 + + const dateMs = Date.parse(header) + if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now()) + + return undefined +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +function backoffWithJitter(attempt: number): number { + const base = 1000 * 2 ** attempt + return base / 2 + Math.random() * (base / 2) +} + +// Shared by all three incident.io calls: on a 429, wait for the Retry-After hint (or an +// exponential fallback) and retry, up to MAX_RETRIES times, then return the still-429 response +// to the caller. +async function fetchWithRetry( + url: string, + init: RequestInit, + timeoutMs: number +): Promise { + for (let attempt = 0; ; attempt++) { + const response = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) }) + + if (response.status !== 429 || attempt >= MAX_RETRIES) { + return response + } + + const retryAfterMs = parseRetryAfterMs(response.headers.get('Retry-After')) + await sleep(Math.max(retryAfterMs ?? 0, backoffWithJitter(attempt))) + } +} + +async function fetchWidget(url: string): Promise { + const response = await fetchWithRetry(url, { cache: 'no-store' }, WIDGET_TIMEOUT_MS) + + if (!response.ok) { + const retryAfter = response.headers.get('Retry-After') ?? undefined + const body = await response.text() + throw new InternalServerError(`incident.io Widget API responded with ${response.status}`, { + status: response.status, + body, + ...(retryAfter !== undefined && { retryAfter }), + }) + } + + const json = await response.json() + const result = WidgetResponseSchema.safeParse(json) + if (!result.success) { + throw new InternalServerError('incident.io Widget API response did not match expected schema', { + issues: result.error.issues, + }) + } + + return result.data +} + +async function fetchLinkedResponseIncidentIds( + apiKey: string, + statusPageId: string, + itemId: string +): Promise> { + const response = await fetchWithRetry( + `https://api.incident.io/v1/status-pages/${statusPageId}/incidents/${itemId}/response-incidents`, + { headers: { Authorization: `Bearer ${apiKey}` } }, + LINK_TIMEOUT_MS + ) + + if (response.status === 404) return [] + + if (!response.ok) { + throw new Error(`incident.io response-incidents lookup responded with ${response.status}`) + } + + const json = await response.json() + const result = LinkedResponseIncidentsSchema.parse(json) + return result.incidents.map((incident) => incident.id) +} + +async function fetchResponseIncident(apiKey: string, id: string): Promise { + const response = await fetchWithRetry( + `https://api.incident.io/v2/incidents/${id}`, + { headers: { Authorization: `Bearer ${apiKey}` } }, + INCIDENT_TIMEOUT_MS + ) + + if (!response.ok) { + throw new Error(`incident.io incident lookup responded with ${response.status}`) + } + + const json = await response.json() + return ResponseIncidentSchema.parse(json).incident +} + +export async function getStatusPage(): Promise { + const widgetUrl = process.env.INCIDENT_IO_WIDGET_URL + if (!widgetUrl) { + throw new InternalServerError('INCIDENT_IO_WIDGET_URL is not set') + } + + const widget = await fetchWidget(widgetUrl) + + const modeFieldIds = customContentRaw['status_page:visibility_field_ids'] ?? [''] + + const apiKey = process.env.INCIDENT_IO_API_KEY + const statusPageId = process.env.INCIDENT_IO_STATUS_PAGE_ID + + if (!apiKey || !statusPageId) { + console.error('INCIDENT_IO_API_KEY or INCIDENT_IO_STATUS_PAGE_ID is not set') + return { data: annotateWidget(widget, new Map(), modeFieldIds), isDegraded: true } + } + + let isDegraded = false + + const allItems = [ + ...widget.ongoing_incidents, + ...widget.in_progress_maintenances, + ...widget.scheduled_maintenances, + ] + + const linkResults = await Promise.allSettled( + allItems.map((item) => fetchLinkedResponseIncidentIds(apiKey, statusPageId, item.id)) + ) + + const linkedIdsByItemId = new Map>() + linkResults.forEach((result, index) => { + if (result.status === 'fulfilled') { + linkedIdsByItemId.set(allItems[index].id, result.value) + } else { + isDegraded = true + } + }) + + const uniqueLinkedIds = Array.from(new Set(Array.from(linkedIdsByItemId.values()).flat())) + + const incidentResults = await Promise.allSettled( + uniqueLinkedIds.map((id) => fetchResponseIncident(apiKey, id)) + ) + + const incidentsById = new Map() + incidentResults.forEach((result, index) => { + if (result.status === 'fulfilled') { + incidentsById.set(uniqueLinkedIds[index], result.value) + } else { + isDegraded = true + } + }) + + const flagsById = new Map() + for (const item of allItems) { + const linkedIncidents = (linkedIdsByItemId.get(item.id) ?? []) + .map((id) => incidentsById.get(id)) + .filter((incident): incident is ResponseIncident => incident !== undefined) + + if (linkedIncidents.length > 0) { + flagsById.set( + item.id, + mergeLinkedFlags(linkedIncidents, modeFieldIds, { + hideBannerFieldId: HIDE_BANNER_FIELD_ID, + leadDaysFieldId: BANNER_LEAD_DAYS_FIELD_ID, + }) + ) + } + } + + return { data: annotateWidget(widget, flagsById, modeFieldIds), isDegraded } +} diff --git a/apps/studio/lib/api/status-page.utils.test.ts b/apps/studio/lib/api/status-page.utils.test.ts new file mode 100644 index 0000000000000..cc93bf75a6b7c --- /dev/null +++ b/apps/studio/lib/api/status-page.utils.test.ts @@ -0,0 +1,319 @@ +import { describe, expect, test } from 'vitest' + +import { + annotateWidget, + computeAnnotations, + mergeLinkedFlags, + readCustomFieldFlag, + readCustomFieldNumber, + type CustomFieldEntry, + type ResponseIncident, + type ResponseIncidentFlags, +} from './status-page.utils' +import type { WidgetResponse } from '@/lib/status-page/status-page.schema' + +const FIELD_ID = 'field-1' +const OTHER_FIELD_ID = 'field-2' + +function entriesWith(fieldId: string, values: CustomFieldEntry['values']): Array { + return [{ custom_field: { id: fieldId }, values }] +} + +describe('readCustomFieldFlag', () => { + test('matches via value_option', () => { + const entries = entriesWith(FIELD_ID, [{ value_option: { value: 'true' } }]) + expect(readCustomFieldFlag(entries, FIELD_ID)).toBe(true) + }) + + test('matches via value_text', () => { + const entries = entriesWith(FIELD_ID, [{ value_text: 'true' }]) + expect(readCustomFieldFlag(entries, FIELD_ID)).toBe(true) + }) + + test('matches via value_numeric (non-zero → true)', () => { + const entries = entriesWith(FIELD_ID, [{ value_numeric: '5' }]) + expect(readCustomFieldFlag(entries, FIELD_ID)).toBe(true) + }) + + test('value_numeric of 0 → false', () => { + const entries = entriesWith(FIELD_ID, [{ value_numeric: '0' }]) + expect(readCustomFieldFlag(entries, FIELD_ID)).toBe(false) + }) + + test('value_numeric negative → false', () => { + const entries = entriesWith(FIELD_ID, [{ value_numeric: '-5' }]) + expect(readCustomFieldFlag(entries, FIELD_ID)).toBe(false) + }) + + test('missing entry → false', () => { + const entries = entriesWith(OTHER_FIELD_ID, [{ value_text: 'true' }]) + expect(readCustomFieldFlag(entries, FIELD_ID)).toBe(false) + }) + + test('empty fieldId → false (no-op)', () => { + const entries = entriesWith(FIELD_ID, [{ value_text: 'true' }]) + expect(readCustomFieldFlag(entries, '')).toBe(false) + }) + + test('is case-insensitive', () => { + const entries = entriesWith(FIELD_ID, [{ value_option: { value: 'TRUE' } }]) + expect(readCustomFieldFlag(entries, FIELD_ID)).toBe(true) + + const entries2 = entriesWith(FIELD_ID, [{ value_text: 'Yes' }]) + expect(readCustomFieldFlag(entries2, FIELD_ID)).toBe(true) + }) + + test('trims whitespace', () => { + const entries = entriesWith(FIELD_ID, [{ value_text: ' true ' }]) + expect(readCustomFieldFlag(entries, FIELD_ID)).toBe(true) + }) +}) + +describe('readCustomFieldNumber', () => { + test('reads a valid positive value via value_numeric', () => { + const entries = entriesWith(FIELD_ID, [{ value_numeric: '7' }]) + expect(readCustomFieldNumber(entries, FIELD_ID)).toBe(7) + }) + + test('reads a valid positive value via value_option', () => { + const entries = entriesWith(FIELD_ID, [{ value_option: { value: '3' } }]) + expect(readCustomFieldNumber(entries, FIELD_ID)).toBe(3) + }) + + test('reads a valid positive value via value_text', () => { + const entries = entriesWith(FIELD_ID, [{ value_text: '10' }]) + expect(readCustomFieldNumber(entries, FIELD_ID)).toBe(10) + }) + + test('0 → null', () => { + const entries = entriesWith(FIELD_ID, [{ value_numeric: '0' }]) + expect(readCustomFieldNumber(entries, FIELD_ID)).toBe(null) + }) + + test('negative number → null', () => { + const entries = entriesWith(FIELD_ID, [{ value_numeric: '-5' }]) + expect(readCustomFieldNumber(entries, FIELD_ID)).toBe(null) + }) + + test('non-numeric string (NaN) → null', () => { + const entries = entriesWith(FIELD_ID, [{ value_text: 'not-a-number' }]) + expect(readCustomFieldNumber(entries, FIELD_ID)).toBe(null) + }) + + test('empty fieldId → null', () => { + const entries = entriesWith(FIELD_ID, [{ value_numeric: '7' }]) + expect(readCustomFieldNumber(entries, '')).toBe(null) + }) + + test('missing entry → null', () => { + const entries = entriesWith(OTHER_FIELD_ID, [{ value_numeric: '7' }]) + expect(readCustomFieldNumber(entries, FIELD_ID)).toBe(null) + }) +}) + +describe('mergeLinkedFlags', () => { + const fieldIds = { hideBannerFieldId: 'hide-banner', leadDaysFieldId: 'lead-days' } + + test('OR-merges modeFlags across multiple linked incidents', () => { + const incidentA: ResponseIncident = { + id: 'a', + custom_field_entries: entriesWith('mode-1', [{ value_text: 'true' }]), + } + const incidentB: ResponseIncident = { + id: 'b', + custom_field_entries: entriesWith('mode-1', [{ value_text: 'false' }]), + } + + const result = mergeLinkedFlags([incidentA, incidentB], ['mode-1'], fieldIds) + expect(result.modeFlags['mode-1']).toBe(true) + }) + + test('AND-merges hideBanner: all must have it set for it to end up true', () => { + const incidentA: ResponseIncident = { + id: 'a', + custom_field_entries: entriesWith('hide-banner', [{ value_text: 'true' }]), + } + const incidentB: ResponseIncident = { + id: 'b', + custom_field_entries: entriesWith('hide-banner', [{ value_text: 'true' }]), + } + + const result = mergeLinkedFlags([incidentA, incidentB], [], fieldIds) + expect(result.hideBanner).toBe(true) + }) + + test('one incident missing hideBanner brings the merge to false', () => { + const incidentA: ResponseIncident = { + id: 'a', + custom_field_entries: entriesWith('hide-banner', [{ value_text: 'true' }]), + } + const incidentB: ResponseIncident = { + id: 'b', + custom_field_entries: entriesWith('hide-banner', [{ value_text: 'false' }]), + } + + const result = mergeLinkedFlags([incidentA, incidentB], [], fieldIds) + expect(result.hideBanner).toBe(false) + }) + + test('leadDays picks the max of the non-null values across incidents', () => { + const incidentA: ResponseIncident = { + id: 'a', + custom_field_entries: entriesWith('lead-days', [{ value_numeric: '3' }]), + } + const incidentB: ResponseIncident = { + id: 'b', + custom_field_entries: entriesWith('lead-days', [{ value_numeric: '10' }]), + } + + const result = mergeLinkedFlags([incidentA, incidentB], [], fieldIds) + expect(result.leadDays).toBe(10) + }) + + test('leadDays is null when none set it', () => { + const incidentA: ResponseIncident = { id: 'a', custom_field_entries: [] } + + const result = mergeLinkedFlags([incidentA], [], fieldIds) + expect(result.leadDays).toBe(null) + }) +}) + +describe('computeAnnotations', () => { + test('flags undefined → all-permissive defaults', () => { + expect(computeAnnotations(undefined, [''])).toEqual({ + visible: true, + show_banner: true, + banner_lead_days: null, + }) + }) + + test('hideBanner true → show_banner false', () => { + const flags: ResponseIncidentFlags = { hideBanner: true, modeFlags: {}, leadDays: null } + expect(computeAnnotations(flags, ['']).show_banner).toBe(false) + }) + + test('hideBanner false → show_banner true', () => { + const flags: ResponseIncidentFlags = { hideBanner: false, modeFlags: {}, leadDays: null } + expect(computeAnnotations(flags, ['']).show_banner).toBe(true) + }) + + test('own mode ID empty + another mode flag set → hidden', () => { + const flags: ResponseIncidentFlags = { + hideBanner: false, + modeFlags: { 'other-mode': true }, + leadDays: null, + } + expect(computeAnnotations(flags, ['', 'other-mode']).visible).toBe(false) + }) + + test('own mode ID set + own flag true, even if another is also true → visible', () => { + const flags: ResponseIncidentFlags = { + hideBanner: false, + modeFlags: { 'own-mode': true, 'other-mode': true }, + leadDays: null, + } + expect(computeAnnotations(flags, ['own-mode', 'other-mode']).visible).toBe(true) + }) + + test('own mode ID set + own flag false + another flag true → hidden', () => { + const flags: ResponseIncidentFlags = { + hideBanner: false, + modeFlags: { 'own-mode': false, 'other-mode': true }, + leadDays: null, + } + expect(computeAnnotations(flags, ['own-mode', 'other-mode']).visible).toBe(false) + }) + + test('nothing set anywhere → visible', () => { + const flags: ResponseIncidentFlags = { + hideBanner: false, + modeFlags: { 'own-mode': false, 'other-mode': false }, + leadDays: null, + } + expect(computeAnnotations(flags, ['own-mode', 'other-mode']).visible).toBe(true) + }) +}) + +describe('annotateWidget', () => { + const baseIncident = { + id: 'incident-1', + name: 'Incident 1', + url: 'https://status.example.com/incidents/incident-1', + last_update_at: '2026-01-01T00:00:00Z', + last_update_message: 'Investigating', + affected_components: [], + status: 'investigating' as const, + current_worst_impact: 'partial_outage' as const, + } + + const baseInProgressMaintenance = { + id: 'maintenance-1', + name: 'Maintenance 1', + url: 'https://status.example.com/maintenances/maintenance-1', + last_update_at: '2026-01-01T00:00:00Z', + last_update_message: null, + affected_components: [], + status: 'maintenance_in_progress' as const, + started_at: '2026-01-01T00:00:00Z', + scheduled_end_at: null, + } + + const baseScheduledMaintenance = { + id: 'scheduled-1', + name: 'Scheduled 1', + url: 'https://status.example.com/maintenances/scheduled-1', + last_update_at: '2026-01-01T00:00:00Z', + last_update_message: null, + affected_components: [], + status: 'maintenance_scheduled' as const, + starts_at: '2026-02-01T00:00:00Z', + ends_at: '2026-02-02T00:00:00Z', + } + + const widget: WidgetResponse = { + page_title: 'Supabase Status', + page_url: 'https://status.example.com', + ongoing_incidents: [baseIncident], + in_progress_maintenances: [baseInProgressMaintenance], + scheduled_maintenances: [baseScheduledMaintenance], + } + + test('preserves non-annotation fields, and gates visibility/annotations per item', () => { + const flagsById = new Map([ + ['incident-1', { hideBanner: true, modeFlags: {}, leadDays: 5 }], + ]) + + const result = annotateWidget(widget, flagsById, ['']) + + expect(result.page_title).toBe('Supabase Status') + expect(result.page_url).toBe('https://status.example.com') + + expect(result.ongoing_incidents[0]).toMatchObject({ + ...baseIncident, + visible: true, + show_banner: false, + }) + + expect(result.ongoing_incidents[0]).not.toHaveProperty('banner_lead_days') + expect(result.in_progress_maintenances[0]).not.toHaveProperty('banner_lead_days') + expect(result.scheduled_maintenances[0]).toHaveProperty('banner_lead_days') + }) + + test('an item absent from flagsById gets the full default annotations', () => { + const result = annotateWidget(widget, new Map(), ['']) + + expect(result.ongoing_incidents[0]).toMatchObject({ + visible: true, + show_banner: true, + }) + expect(result.in_progress_maintenances[0]).toMatchObject({ + visible: true, + show_banner: true, + }) + expect(result.scheduled_maintenances[0]).toMatchObject({ + visible: true, + show_banner: true, + banner_lead_days: null, + }) + }) +}) diff --git a/apps/studio/lib/api/status-page.utils.ts b/apps/studio/lib/api/status-page.utils.ts new file mode 100644 index 0000000000000..da51fca414e00 --- /dev/null +++ b/apps/studio/lib/api/status-page.utils.ts @@ -0,0 +1,138 @@ +import type { StatusPageResponse, WidgetResponse } from '@/lib/status-page/status-page.schema' + +export type CustomFieldValue = { + value_option?: { value: string } | null + value_text?: string | null + value_numeric?: string | null +} + +export type CustomFieldEntry = { + custom_field: { id: string } + values: Array +} + +export type ResponseIncident = { + id: string + custom_field_entries: Array +} + +const TRUE_VALUES = new Set(['yes', 'true', '1']) + +export function readCustomFieldFlag(entries: Array, fieldId: string): boolean { + if (!fieldId) return false + + const entry = entries.find((e) => e.custom_field.id === fieldId) + if (!entry) return false + + return entry.values.some((value) => { + const text = (value.value_option?.value ?? value.value_text ?? '').trim().toLowerCase() + if (TRUE_VALUES.has(text)) return true + + if (value.value_numeric != null) { + const numeric = Number(value.value_numeric) + return Number.isFinite(numeric) && numeric > 0 + } + + return false + }) +} + +export function readCustomFieldNumber( + entries: Array, + fieldId: string +): number | null { + if (!fieldId) return null + + const entry = entries.find((e) => e.custom_field.id === fieldId) + if (!entry) return null + + for (const value of entry.values) { + const raw = value.value_numeric ?? value.value_option?.value ?? value.value_text + if (raw == null) continue + + const numeric = Number(raw) + if (Number.isFinite(numeric) && numeric > 0) return numeric + } + + return null +} + +export type ResponseIncidentFlags = { + hideBanner: boolean + modeFlags: Record + leadDays: number | null +} + +export type ResponseIncidentFieldIds = { + hideBannerFieldId: string + leadDaysFieldId: string +} + +export function mergeLinkedFlags( + incidents: Array, + modeFieldIds: Array, + fieldIds: ResponseIncidentFieldIds +): ResponseIncidentFlags { + const modeFlags: Record = {} + for (const fieldId of modeFieldIds.filter(Boolean)) { + modeFlags[fieldId] = incidents.some((incident) => + readCustomFieldFlag(incident.custom_field_entries, fieldId) + ) + } + + const hideBanner = incidents.every((incident) => + readCustomFieldFlag(incident.custom_field_entries, fieldIds.hideBannerFieldId) + ) + + const leadDaysValues = incidents + .map((incident) => + readCustomFieldNumber(incident.custom_field_entries, fieldIds.leadDaysFieldId) + ) + .filter((value): value is number => value !== null) + + return { + hideBanner, + modeFlags, + leadDays: leadDaysValues.length > 0 ? Math.max(...leadDaysValues) : null, + } +} + +export function computeAnnotations( + flags: ResponseIncidentFlags | undefined, + modeFieldIds: Array +): { visible: boolean; show_banner: boolean; banner_lead_days: number | null } { + if (!flags) { + return { visible: true, show_banner: true, banner_lead_days: null } + } + + const [own = ''] = modeFieldIds + const anySet = modeFieldIds.filter(Boolean).some((id) => flags.modeFlags[id]) + const visible = !anySet || (own !== '' && flags.modeFlags[own] === true) + + return { + visible, + show_banner: !flags.hideBanner, + banner_lead_days: flags.leadDays, + } +} + +export function annotateWidget( + widget: WidgetResponse, + flagsById: Map, + modeFieldIds: Array +): StatusPageResponse { + const withBaseAnnotations = (item: T) => { + const { visible, show_banner } = computeAnnotations(flagsById.get(item.id), modeFieldIds) + return { ...item, visible, show_banner } + } + + return { + ...widget, + ongoing_incidents: widget.ongoing_incidents.map(withBaseAnnotations), + in_progress_maintenances: widget.in_progress_maintenances.map(withBaseAnnotations), + scheduled_maintenances: widget.scheduled_maintenances.map((item) => { + const annotations = computeAnnotations(flagsById.get(item.id), modeFieldIds) + return { ...item, ...annotations } + }), + } +} diff --git a/apps/studio/lib/boot-fallback-copy.ts b/apps/studio/lib/boot-fallback-copy.ts new file mode 100644 index 0000000000000..8f49994fe9bc2 --- /dev/null +++ b/apps/studio/lib/boot-fallback-copy.ts @@ -0,0 +1,6 @@ +// Shared between ShellFallback (TanStack) and BootTimeoutFallback (Next.js) — +// same "stuck loading" message, different markup per runtime (see FE-4460). +export const BOOT_FALLBACK_REVEAL_DELAY_SECONDS = 7 +export const BOOT_FALLBACK_SUPPORT_EMAIL = 'support@supabase.io' +export const BOOT_FALLBACK_MESSAGE = + 'Taking longer than expected? Try clearing your browser cookies and reloading the page.' diff --git a/apps/studio/lib/hosted-api-allowlist.ts b/apps/studio/lib/hosted-api-allowlist.ts index 8d70221994d32..59c7b0c80bff5 100644 --- a/apps/studio/lib/hosted-api-allowlist.ts +++ b/apps/studio/lib/hosted-api-allowlist.ts @@ -23,6 +23,7 @@ export const HOSTED_SUPPORTED_API_URLS = [ '/generate-attachment-url', '/incident-status', '/incident-banner', + '/status-page', '/status-override', '/api/integrations/stripe-sync', '/content/graphql', diff --git a/apps/studio/lib/status-page/status-page.schema.test.ts b/apps/studio/lib/status-page/status-page.schema.test.ts new file mode 100644 index 0000000000000..f3bee36f631d2 --- /dev/null +++ b/apps/studio/lib/status-page/status-page.schema.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, expectTypeOf, test, vi } from 'vitest' +import { z } from 'zod' + +import { + WidgetIncidentSchema, + WidgetInProgressMaintenanceSchema, + WidgetResponseSchema, + WidgetScheduledMaintenanceSchema, +} from './status-page.schema' + +describe('WidgetResponseSchema: tolerant arrays (fail-open)', () => { + test('drops a malformed item from one array without failing the whole parse, leaving the valid item in that array and the other arrays intact', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + const payload = { + page_title: 'Supabase', + page_url: 'https://statuspage.incident.io/supabase', + ongoing_incidents: [ + { + id: 'incident-valid', + name: 'Valid incident', + url: 'https://status.example.com/incidents/incident-valid', + last_update_at: '2026-01-01T00:00:00Z', + status: 'identified', + current_worst_impact: 'partial_outage', + }, + // Malformed: missing required `id`/`name`/`url`/`last_update_at`. + { status: 'identified' }, + ], + in_progress_maintenances: [ + { + id: 'maintenance-valid', + name: 'Valid maintenance', + url: 'https://status.example.com/maintenances/maintenance-valid', + last_update_at: '2026-01-01T00:00:00Z', + status: 'maintenance_in_progress', + started_at: '2026-01-01T00:00:00Z', + }, + ], + scheduled_maintenances: [], + } + + const result = WidgetResponseSchema.safeParse(payload) + + expect(result.success).toBe(true) + if (!result.success) return + + // The other valid item in the same array survives... + expect(result.data.ongoing_incidents).toHaveLength(1) + expect(result.data.ongoing_incidents[0].id).toBe('incident-valid') + // ...and the other arrays are unaffected by the malformed item elsewhere. + expect(result.data.in_progress_maintenances).toHaveLength(1) + expect(result.data.in_progress_maintenances[0].id).toBe('maintenance-valid') + + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('ongoing_incidents'), + expect.any(Object) + ) + + warnSpy.mockRestore() + }) + + test('defaults a missing array key to an empty array', () => { + const result = WidgetResponseSchema.safeParse({ + page_title: 'Supabase', + page_url: 'https://statuspage.incident.io/supabase', + }) + + expect(result.success).toBe(true) + if (!result.success) return + + expect(result.data.ongoing_incidents).toEqual([]) + expect(result.data.in_progress_maintenances).toEqual([]) + expect(result.data.scheduled_maintenances).toEqual([]) + }) + + test('tolerantArray keeps parsed item types concrete, not any', () => { + const result = WidgetResponseSchema.parse({ + page_title: 'Supabase', + page_url: 'https://statuspage.incident.io/supabase', + }) + + expectTypeOf(result.ongoing_incidents).toEqualTypeOf< + Array> + >() + expectTypeOf(result.in_progress_maintenances).toEqualTypeOf< + Array> + >() + expectTypeOf(result.scheduled_maintenances).toEqualTypeOf< + Array> + >() + }) +}) diff --git a/apps/studio/lib/status-page/status-page.schema.ts b/apps/studio/lib/status-page/status-page.schema.ts new file mode 100644 index 0000000000000..f94deb55c42af --- /dev/null +++ b/apps/studio/lib/status-page/status-page.schema.ts @@ -0,0 +1,96 @@ +import { z } from 'zod' + +const ComponentStatus = z.enum([ + 'operational', + 'degraded_performance', + 'partial_outage', + 'full_outage', +]) +const WorstImpact = z.enum(['degraded_performance', 'partial_outage', 'full_outage']) + +export const AffectedComponentSchema = z.object({ + id: z.string(), + name: z.string(), + group_name: z.string().nullish(), + current_status: ComponentStatus.catch('degraded_performance'), +}) + +const Base = { + id: z.string(), + name: z.string(), + url: z.string(), + last_update_at: z.string(), + last_update_message: z.string().nullish(), + affected_components: z.array(AffectedComponentSchema).default([]), +} + +export const WidgetIncidentSchema = z.object({ + ...Base, + status: z.enum(['investigating', 'identified', 'monitoring']).catch('investigating'), + current_worst_impact: WorstImpact.catch('full_outage'), +}) + +export const WidgetInProgressMaintenanceSchema = z.object({ + ...Base, + status: z.literal('maintenance_in_progress').catch('maintenance_in_progress'), + started_at: z.string(), + scheduled_end_at: z.string().nullish(), +}) + +export const WidgetScheduledMaintenanceSchema = z.object({ + ...Base, + status: z.literal('maintenance_scheduled').catch('maintenance_scheduled'), + starts_at: z.string().nullish(), + ends_at: z.string().nullish(), +}) + +function tolerantArray(itemSchema: T, arrayName: string) { + return z + .array(z.unknown()) + .default([]) + .transform( + (items): Array> => + items.flatMap((item) => { + const result = itemSchema.safeParse(item) + if (result.success) return [result.data] + + const rawId = + typeof item === 'object' && item !== null && 'id' in item + ? (item as { id?: unknown }).id + : undefined + console.warn(`incident.io Widget API: dropping malformed item in ${arrayName}`, { + id: rawId, + issues: result.error.issues, + }) + return [] + }) + ) +} + +export const WidgetResponseSchema = z.object({ + page_title: z.string(), + page_url: z.string(), + ongoing_incidents: tolerantArray(WidgetIncidentSchema, 'ongoing_incidents'), + in_progress_maintenances: tolerantArray( + WidgetInProgressMaintenanceSchema, + 'in_progress_maintenances' + ), + scheduled_maintenances: tolerantArray(WidgetScheduledMaintenanceSchema, 'scheduled_maintenances'), +}) + +const Annotations = { visible: z.boolean(), show_banner: z.boolean() } + +export const StatusPageResponseSchema = WidgetResponseSchema.extend({ + ongoing_incidents: z.array(WidgetIncidentSchema.extend(Annotations)), + in_progress_maintenances: z.array(WidgetInProgressMaintenanceSchema.extend(Annotations)), + scheduled_maintenances: z.array( + WidgetScheduledMaintenanceSchema.extend({ + ...Annotations, + banner_lead_days: z.number().positive().nullable(), + }) + ), +}) + +export type WidgetResponse = z.infer +export type StatusPageResponse = z.infer +export type AffectedComponent = z.infer diff --git a/apps/studio/pages/_app.tsx b/apps/studio/pages/_app.tsx index 5d913d8958553..576baa72ac1e2 100644 --- a/apps/studio/pages/_app.tsx +++ b/apps/studio/pages/_app.tsx @@ -48,6 +48,7 @@ import { MonacoThemeProvider } from '@/components/interfaces/App/MonacoThemeProv import { RouteValidationWrapper } from '@/components/interfaces/App/RouteValidationWrapper' import { MainScrollContainerProvider } from '@/components/layouts/MainScrollContainerContext' import { BannerStackProvider } from '@/components/ui/BannerStack/BannerStackProvider' +import { clearBootTimeoutFallback } from '@/components/ui/BootTimeoutFallback/BootTimeoutFallback' import { GlobalErrorBoundaryState } from '@/components/ui/ErrorBoundary/GlobalErrorBoundaryState' import { GlobalShortcuts } from '@/components/ui/GlobalShortcuts/GlobalShortcuts' import { getCLIReleaseVersion } from '@/data/misc/cli-release-version-query' @@ -157,6 +158,10 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) { useThemeSandbox() + useEffect(() => { + clearBootTimeoutFallback() + }, []) + const isTestEnv = process.env.NEXT_PUBLIC_NODE_ENV === 'test' // [Joshen] Should target hosted staging, local dev, and local CLI only diff --git a/apps/studio/pages/_document.tsx b/apps/studio/pages/_document.tsx index 8560136df0f42..9edaf781a02c1 100644 --- a/apps/studio/pages/_document.tsx +++ b/apps/studio/pages/_document.tsx @@ -1,5 +1,6 @@ import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document' +import { BootTimeoutFallback } from '@/components/ui/BootTimeoutFallback/BootTimeoutFallback' import { inter, manrope, sourceCodePro } from '@/fonts' class MyDocument extends Document { @@ -14,6 +15,7 @@ class MyDocument extends Document { +
diff --git a/apps/studio/routeTree.gen.ts b/apps/studio/routeTree.gen.ts index 7b18ae8613434..f51bca5bf4017 100644 --- a/apps/studio/routeTree.gen.ts +++ b/apps/studio/routeTree.gen.ts @@ -27,6 +27,7 @@ import { Route as OrgChar91_Char93RouteImport } from './routes/org.[_]' import { Route as NewSlugRouteImport } from './routes/new/$slug' import { Route as McpSecretsRouteImport } from './routes/mcp/secrets' import { Route as IntegrationsVercelRouteImport } from './routes/integrations/vercel' +import { Route as ApiStatusPageRouteImport } from './routes/api/status-page' import { Route as ApiStatusOverrideRouteImport } from './routes/api/status-override' import { Route as ApiScopedAccessTokenPermissionsRouteImport } from './routes/api/scoped-access-token-permissions' import { Route as ApiParseQueryRouteImport } from './routes/api/parse-query' @@ -422,6 +423,11 @@ const IntegrationsVercelRoute = IntegrationsVercelRouteImport.update({ path: '/integrations/vercel', getParentRoute: () => rootRouteImport, } as any) +const ApiStatusPageRoute = ApiStatusPageRouteImport.update({ + id: '/api/status-page', + path: '/api/status-page', + getParentRoute: () => rootRouteImport, +} as any) const ApiStatusOverrideRoute = ApiStatusOverrideRouteImport.update({ id: '/api/status-override', path: '/api/status-override', @@ -2175,6 +2181,7 @@ export interface FileRoutesByFullPath { '/api/parse-query': typeof ApiParseQueryRoute '/api/scoped-access-token-permissions': typeof ApiScopedAccessTokenPermissionsRoute '/api/status-override': typeof ApiStatusOverrideRoute + '/api/status-page': typeof ApiStatusPageRoute '/integrations/vercel': typeof IntegrationsVercelRouteWithChildren '/mcp/secrets': typeof McpSecretsRoute '/new/$slug': typeof NewSlugRoute @@ -2497,6 +2504,7 @@ export interface FileRoutesByTo { '/api/parse-query': typeof ApiParseQueryRoute '/api/scoped-access-token-permissions': typeof ApiScopedAccessTokenPermissionsRoute '/api/status-override': typeof ApiStatusOverrideRoute + '/api/status-page': typeof ApiStatusPageRoute '/integrations/vercel': typeof IntegrationsVercelRouteWithChildren '/mcp/secrets': typeof McpSecretsRoute '/new/$slug': typeof NewSlugRoute @@ -2809,6 +2817,7 @@ export interface FileRoutesById { '/api/parse-query': typeof ApiParseQueryRoute '/api/scoped-access-token-permissions': typeof ApiScopedAccessTokenPermissionsRoute '/api/status-override': typeof ApiStatusOverrideRoute + '/api/status-page': typeof ApiStatusPageRoute '/integrations/vercel': typeof IntegrationsVercelRouteWithChildren '/mcp/secrets': typeof McpSecretsRoute '/new/$slug': typeof NewSlugRoute @@ -3134,6 +3143,7 @@ export interface FileRouteTypes { | '/api/parse-query' | '/api/scoped-access-token-permissions' | '/api/status-override' + | '/api/status-page' | '/integrations/vercel' | '/mcp/secrets' | '/new/$slug' @@ -3456,6 +3466,7 @@ export interface FileRouteTypes { | '/api/parse-query' | '/api/scoped-access-token-permissions' | '/api/status-override' + | '/api/status-page' | '/integrations/vercel' | '/mcp/secrets' | '/new/$slug' @@ -3767,6 +3778,7 @@ export interface FileRouteTypes { | '/api/parse-query' | '/api/scoped-access-token-permissions' | '/api/status-override' + | '/api/status-page' | '/integrations/vercel' | '/mcp/secrets' | '/new/$slug' @@ -4081,6 +4093,7 @@ export interface RootRouteChildren { ApiParseQueryRoute: typeof ApiParseQueryRoute ApiScopedAccessTokenPermissionsRoute: typeof ApiScopedAccessTokenPermissionsRoute ApiStatusOverrideRoute: typeof ApiStatusOverrideRoute + ApiStatusPageRoute: typeof ApiStatusPageRoute IntegrationsVercelRoute: typeof IntegrationsVercelRouteWithChildren McpSecretsRoute: typeof McpSecretsRoute NewSlugRoute: typeof NewSlugRoute @@ -4309,6 +4322,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IntegrationsVercelRouteImport parentRoute: typeof rootRouteImport } + '/api/status-page': { + id: '/api/status-page' + path: '/api/status-page' + fullPath: '/api/status-page' + preLoaderRoute: typeof ApiStatusPageRouteImport + parentRoute: typeof rootRouteImport + } '/api/status-override': { id: '/api/status-override' path: '/api/status-override' @@ -7207,6 +7227,7 @@ const rootRouteChildren: RootRouteChildren = { ApiParseQueryRoute: ApiParseQueryRoute, ApiScopedAccessTokenPermissionsRoute: ApiScopedAccessTokenPermissionsRoute, ApiStatusOverrideRoute: ApiStatusOverrideRoute, + ApiStatusPageRoute: ApiStatusPageRoute, IntegrationsVercelRoute: IntegrationsVercelRouteWithChildren, McpSecretsRoute: McpSecretsRoute, NewSlugRoute: NewSlugRoute, diff --git a/apps/studio/routes/api/status-page.ts b/apps/studio/routes/api/status-page.ts new file mode 100644 index 0000000000000..655d9f23e5e06 --- /dev/null +++ b/apps/studio/routes/api/status-page.ts @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { GET, HEAD, OPTIONS } from '@/app/api/status-page/route' + +export const Route = createFileRoute('/api/status-page')({ + server: { + handlers: { + GET: () => GET(), + HEAD: () => HEAD(), + OPTIONS: () => OPTIONS(), + }, + }, +}) diff --git a/apps/studio/scripts/ratchet-rules.json b/apps/studio/scripts/ratchet-rules.json index 4f34402cd7887..9a9bc52b99d36 100644 --- a/apps/studio/scripts/ratchet-rules.json +++ b/apps/studio/scripts/ratchet-rules.json @@ -3,22 +3,12 @@ "import/no-anonymous-default-export", "@tanstack/query/exhaustive-deps", "@typescript-eslint/no-explicit-any", - "no-restricted-imports", "no-restricted-exports", "react/no-unstable-nested-components", - "jsx-a11y/aria-props", - "jsx-a11y/aria-proptypes", - "jsx-a11y/role-supports-aria-props", - "jsx-a11y/anchor-has-content", "jsx-a11y/control-has-associated-label", "jsx-a11y/label-has-associated-control", - "jsx-a11y/aria-role", "jsx-a11y/no-redundant-roles", - "jsx-a11y/no-aria-hidden-on-focusable", - "jsx-a11y/tabindex-no-positive", "jsx-a11y/anchor-is-valid", "jsx-a11y/heading-has-content", - "jsx-a11y/no-distracting-elements", - "valtio/state-snapshot-rule", - "react-hook-form/no-use-watch" + "valtio/state-snapshot-rule" ] diff --git a/apps/studio/turbo.jsonc b/apps/studio/turbo.jsonc index 678cd30ed30f7..bb57b7c8670b9 100644 --- a/apps/studio/turbo.jsonc +++ b/apps/studio/turbo.jsonc @@ -105,6 +105,8 @@ "STATUSPAGE_API_KEY", "STATUSPAGE_PAGE_ID", "INCIDENT_IO_API_KEY", + "INCIDENT_IO_WIDGET_URL", + "INCIDENT_IO_STATUS_PAGE_ID", "LIVE_SUPABASE_SECRET_KEY", // Selects the build mode for the studio's `build`/`start` scripts // (e.g. e2e sets `MODE=test`). Listed so turbo invalidates the diff --git a/packages/icons/__registry__/index.tsx b/packages/icons/__registry__/index.tsx index 5e95c8372430c..196301a9b4714 100644 --- a/packages/icons/__registry__/index.tsx +++ b/packages/icons/__registry__/index.tsx @@ -128,10 +128,10 @@ export const Index: Record = [ name: "compute", componentName: "Compute", deprecated: false, - raw: "import createSupabaseIcon from '../createSupabaseIcon';\n\n/**\n * @component @name Compute\n * @description Supabase SVG icon component, renders SVG Element with children.\n *\n * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjMiIHZpZXdCb3g9IjAgMCAyNCAyMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBzdHJva2U9IiMwMDAiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOyBib3JkZXItcmFkaXVzOiAycHgiIHN0cm9rZS13aWR0aD0iMS41Ij48cGF0aCBkPSJNNy41NTU1NiA4LjcwMDQ5TDEyIDExLjUwMTFNMTIgMTEuNTAxMUwxNi40NDQ0IDguNzAwNDlNMTIgMTEuNTAxMUwxMiAxNi40MDFNMjIgMTUuNzAwOVY3LjI5OTE0QzIxLjk5OTYgNi45MzA4IDIxLjg5NjcgNi41NjkwNCAyMS43MDE3IDYuMjUwMTRDMjEuNTA2NyA1LjkzMTI1IDIxLjIyNjQgNS42NjY0MyAyMC44ODg5IDUuNDgyMjdMMTMuMTExMSAxLjI4MTRDMTIuNzczMyAxLjA5NzA1IDEyLjM5MDEgMSAxMiAxQzExLjYwOTkgMSAxMS4yMjY3IDEuMDk3MDUgMTAuODg4OSAxLjI4MTRMMy4xMTExMSA1LjQ4MjI3QzIuNzczNjMgNS42NjY0MyAyLjQ5MzMxIDUuOTMxMjUgMi4yOTgyOSA2LjI1MDE0QzIuMTAzMjcgNi41NjkwNCAyLjAwMDQgNi45MzA4IDIgNy4yOTkxNFYxNS43MDA5QzIuMDAwNCAxNi4wNjkyIDIuMTAzMjcgMTYuNDMxIDIuMjk4MjkgMTYuNzQ5OUMyLjQ5MzMxIDE3LjA2ODggMi43NzM2MyAxNy4zMzM2IDMuMTExMTEgMTcuNTE3N0wxMC44ODg5IDIxLjcxODZDMTEuMjI2NyAyMS45MDI5IDExLjYwOTkgMjIgMTIgMjJDMTIuMzkwMSAyMiAxMi43NzMzIDIxLjkwMjkgMTMuMTExMSAyMS43MTg2TDIwLjg4ODkgMTcuNTE3N0MyMS4yMjY0IDE3LjMzMzYgMjEuNTA2NyAxNy4wNjg4IDIxLjcwMTcgMTYuNzQ5OUMyMS44OTY3IDE2LjQzMSAyMS45OTk2IDE2LjA2OTIgMjIgMTUuNzAwOVpNMTcuOTI1OSAxMy43NDJWOS4yNjAxNUMxNy45MjU3IDkuMDYzNjYgMTcuODY0NyA4Ljg3MDY4IDE3Ljc0OTIgOC43MDA1N0MxNy42MzM2IDguNTMwNDYgMTcuNDY3NSA4LjM4OTE5IDE3LjI2NzUgOC4yOTA5NUwxMi42NTg0IDYuMDUwMDNDMTIuNDU4MiA1Ljk1MTY5IDEyLjIzMTIgNS44OTk5MiAxMiA1Ljg5OTkyQzExLjc2ODggNS44OTk5MiAxMS41NDE4IDUuOTUxNjkgMTEuMzQxNiA2LjA1MDAzTDYuNzMyNTEgOC4yOTA5NUM2LjUzMjUyIDguMzg5MTkgNi4zNjY0MSA4LjUzMDQ2IDYuMjUwODQgOC43MDA1N0M2LjEzNTI3IDguODcwNjggNi4wNzQzMSA5LjA2MzY2IDYuMDc0MDcgOS4yNjAxNVYxMy43NDJDNi4wNzQzMSAxMy45Mzg1IDYuMTM1MjcgMTQuMTMxNSA2LjI1MDg0IDE0LjMwMTZDNi4zNjY0MSAxNC40NzE3IDYuNTMyNTIgMTQuNjEyOSA2LjczMjUxIDE0LjcxMTJMMTEuMzQxNiAxNi45NTIxQzExLjU0MTggMTcuMDUwNCAxMS43Njg4IDE3LjEwMjIgMTIgMTcuMTAyMkMxMi4yMzEyIDE3LjEwMjIgMTIuNDU4MiAxNy4wNTA0IDEyLjY1ODQgMTYuOTUyMUwxNy4yNjc1IDE0LjcxMTJDMTcuNDY3NSAxNC42MTI5IDE3LjYzMzYgMTQuNDcxNyAxNy43NDkyIDE0LjMwMTZDMTcuODY0NyAxNC4xMzE1IDE3LjkyNTcgMTMuOTM4NSAxNy45MjU5IDEzLjc0MloiLz4KPC9zdmc+Cg==)\n *\n * @param {Object} props - Supabase icons props and any valid SVG attribute\n * @returns {JSX.Element} JSX Element\n *\n */\nconst Compute = createSupabaseIcon(\n 'Compute',\n [\n [\n 'path',\n {\n d: 'M7.55556 8.70049L12 11.5011M12 11.5011L16.4444 8.70049M12 11.5011L12 16.401M22 15.7009V7.29914C21.9996 6.9308 21.8967 6.56904 21.7017 6.25014C21.5067 5.93125 21.2264 5.66643 20.8889 5.48227L13.1111 1.2814C12.7733 1.09705 12.3901 1 12 1C11.6099 1 11.2267 1.09705 10.8889 1.2814L3.11111 5.48227C2.77363 5.66643 2.49331 5.93125 2.29829 6.25014C2.10327 6.56904 2.0004 6.9308 2 7.29914V15.7009C2.0004 16.0692 2.10327 16.431 2.29829 16.7499C2.49331 17.0688 2.77363 17.3336 3.11111 17.5177L10.8889 21.7186C11.2267 21.9029 11.6099 22 12 22C12.3901 22 12.7733 21.9029 13.1111 21.7186L20.8889 17.5177C21.2264 17.3336 21.5067 17.0688 21.7017 16.7499C21.8967 16.431 21.9996 16.0692 22 15.7009ZM17.9259 13.742V9.26015C17.9257 9.06366 17.8647 8.87068 17.7492 8.70057C17.6336 8.53046 17.4675 8.38919 17.2675 8.29095L12.6584 6.05003C12.4582 5.95169 12.2312 5.89992 12 5.89992C11.7688 5.89992 11.5418 5.95169 11.3416 6.05003L6.73251 8.29095C6.53252 8.38919 6.36641 8.53046 6.25084 8.70057C6.13527 8.87068 6.07431 9.06366 6.07407 9.26015V13.742C6.07431 13.9385 6.13527 14.1315 6.25084 14.3016C6.36641 14.4717 6.53252 14.6129 6.73251 14.7112L11.3416 16.9521C11.5418 17.0504 11.7688 17.1022 12 17.1022C12.2312 17.1022 12.4582 17.0504 12.6584 16.9521L17.2675 14.7112C17.4675 14.6129 17.6336 14.4717 17.7492 14.3016C17.8647 14.1315 17.9257 13.9385 17.9259 13.742Z',\n key: '1cro8t',\n },\n ],\n ],\n { fill: 'none', stroke: 'currentColor', strokeWidth: '1.5' },\n);\n\nexport default Compute;\n", + raw: "import createSupabaseIcon from '../createSupabaseIcon';\n\n/**\n * @component @name Compute\n * @description Supabase SVG icon component, renders SVG Element with children.\n *\n * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDJweCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuNSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMTIgMTIgNS4zMDktMi41NDdhMS4xNCAxLjE0IDAgMCAwLS40NDItLjM4bC00LjIzMS0yLjIzMUExLjUgMS41IDAgMCAwIDEyIDYuNzAzYTEuMiAxLjIgMCAwIDAtLjU3MS4xMzlMNy4yMTIgOS4wNzRjLS4xODMuMDktLjMzNS4yMjItLjQ0LjM4TTEyIDEyIDYuNzcyIDkuNDUzTTEyIDEydjUuNE02Ljc3MiA5LjQ1M3EwIDAgMCAwbTEwLjUzNy4wMDFhLjkuOSAwIDAgMSAuMTYyLjUxNnY0LjE0MWMwIC4xODItLjA1Ni4zNi0uMTYyLjUxOGExLjE0IDEuMTQgMCAwIDEtLjQ0Mi4zNzhsLTQuMjI0IDIuMjMyYy0uMi4wOTgtLjQyLjE1My0uNjQzLjE2MS0uMjEyIDAtLjM4LS4wNy0uNTYzLS4xNjFsLTQuMjI1LTIuMjMyYTEuMTQgMS4xNCAwIDAgMS0uNDQxLS4zNzguOTMuOTMgMCAwIDEtLjE2Mi0uNTE4VjkuOTdhLjkuOSAwIDAgMSAuMTYzLS41MTdtNC4zNzYtNi44MTNhMiAyIDAgMCAxIDEuOTI3IDBsNy4wOTMgMy45YTIgMiAwIDAgMSAxLjAzNiAxLjc1M3Y3LjYzNWEyIDIgMCAwIDEtMS4wMzYgMS43NTJsLTcuMDkzIDMuOWEyIDIgMCAwIDEtMS45MjcgMGwtNy4wOTQtMy45YTIgMiAwIDAgMS0xLjAzNi0xLjc1MlY4LjI5M0EyIDIgMCAwIDEgNC4wNTQgNi41NHoiLz4KPC9zdmc+)\n *\n * @param {Object} props - Supabase icons props and any valid SVG attribute\n * @returns {JSX.Element} JSX Element\n *\n */\nconst Compute = createSupabaseIcon(\n 'Compute',\n [\n [\n 'path',\n {\n d: 'm12 12 5.309-2.547a1.14 1.14 0 0 0-.442-.38l-4.231-2.231A1.5 1.5 0 0 0 12 6.703a1.2 1.2 0 0 0-.571.139L7.212 9.074c-.183.09-.335.222-.44.38M12 12 6.772 9.453M12 12v5.4M6.772 9.453q0 0 0 0m10.537.001a.9.9 0 0 1 .162.516v4.141c0 .182-.056.36-.162.518a1.14 1.14 0 0 1-.442.378l-4.224 2.232c-.2.098-.42.153-.643.161-.212 0-.38-.07-.563-.161l-4.225-2.232a1.14 1.14 0 0 1-.441-.378.93.93 0 0 1-.162-.518V9.97a.9.9 0 0 1 .163-.517m4.376-6.813a2 2 0 0 1 1.927 0l7.093 3.9a2 2 0 0 1 1.036 1.753v7.635a2 2 0 0 1-1.036 1.752l-7.093 3.9a2 2 0 0 1-1.927 0l-7.094-3.9a2 2 0 0 1-1.036-1.752V8.293A2 2 0 0 1 4.054 6.54z',\n key: 'afg6tn',\n },\n ],\n ],\n {\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: '1.5',\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n },\n);\n\nexport default Compute;\n", component: React.lazy(() => import('icons/src/icons/compute')), import: "import { Compute } from 'icons'", - svg: "\n\n\n", + svg: "\n\n", jsx: "import { Compute } from \"icons\"\n \n " }, { @@ -168,10 +168,10 @@ export const Index: Record = [ name: "elastic", componentName: "Elastic", deprecated: false, - raw: "import createSupabaseIcon from '../createSupabaseIcon';\n\n/**\n * @component @name Elastic\n * @description Supabase SVG icon component, renders SVG Element with children.\n *\n * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBmaWxsPSIjMEI2NEREIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS4yIiBkPSJNMjcuNTY1IDExLjI0MmM1LjEgMS45NCA0Ljg3MiA5LjM5Ni0uMjQ1IDExLjEyN2wtLjE2Mi4wNTUtLjE2Ny0uMDM5LTUuMjgtMS4yMzgtLjI2OC0uMDYzLS4xMjctLjI0NC0xLjQtMi42OS0uMjE3LS40MTcuMzUyLS4zMSA2LjkwNC02LjA3LjI3Mi0uMjQuMzM4LjEzWiIvPgo8cGF0aCBmaWxsPSIjOUFEQzMwIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS4yIiBkPSJtMjIuMDQ3IDIxLjIzOSA0LjggMS4xMjUuMzE2LjA3NC4xMS4zMDQuMDY2LjE5Yy42MjMgMS45NjQtLjI2IDMuNzgtMS42NTIgNC43OTctMS40MzQgMS4wNDgtMy41MSAxLjMyLTUuMTgyLjAyMmwtLjI5LS4yMjUuMDY5LS4zNjEgMS4wMzctNS40NTQuMTE3LS42MTUuNjEuMTQzWiIvPgo8cGF0aCBmaWxsPSIjMUJBOUY1IiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS4yIiBkPSJtNS4wMSA5LjYzIDUuMjY3IDEuMjU1LjI4My4wNjcuMTIyLjI2NCAxLjIzNSAyLjY1LjE4Ny40LS4zMjguMjk4LTYuNzMzIDYuMS0uMjcyLjI0OC0uMzQ1LS4xMzJDMS45MzkgMTkuODMuNzIgMTcuNDU2Ljc1MiAxNS4xNTNjLjAzMi0yLjMwOCAxLjMyMS00LjY0NCAzLjkzMi01LjUwOGwuMTYyLS4wNTQuMTY1LjAzOVoiLz4KPHBhdGggZmlsbD0iI0YwNEU5OCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuMiIgZD0iTTYuMjgxIDQuMzJjMS40MTYtMS4wOCAzLjQ4LTEuMzg3IDUuMjIyLS4wNjlsLjI5Ny4yMjYtLjA3LjM2Ni0xLjA1MyA1LjQ3NC0uMTE4LjYxNS0uNjA5LS4xNDQtNC44LTEuMTM3LS4zMTYtLjA3NS0uMTEtLjMwNmMtLjcwOS0xLjk2Ny4xNDktMy44NzYgMS41NTctNC45NVoiLz4KPHBhdGggZmlsbD0iIzAyQkNCNyIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuMiIgZD0ibTEyLjQ2NiAxNC40MzMgNy4wMyAzLjIxMS4xODguMDg2LjA5NS4xODMgMS41NTUgMi45ODUuMDk2LjE4NC0uMDQuMjA2LTEuMTY1IDYuMTAxLS4wMjQuMTIyLS4wNjguMTAzYy0yLjY4IDMuOTU4LTYuOTAyIDQuNzEtMTAuMjY4IDMuMjYtMy4zNTYtMS40NDctNS44MzQtNS4wNy01LjEyNi05LjczNmwuMDMzLS4yMS4xNTgtLjE0NCA2Ljg4NC02LjI1LjI5My0uMjY1LjM2LjE2NFoiLz4KPHBhdGggZmlsbD0iI0ZFQzUxNCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuMiIgZD0iTTExLjg5MiA0LjQxQzE0LjQzOC42NzYgMTguNzQxLjEwNSAyMi4xMzQgMS41NGMzLjM5MiAxLjQzMyA1Ljk5IDQuOTIgNS4xMDIgOS4zNjJsLS4wMzkuMi0uMTUzLjEzMy03LjA2NiA2LjIxMy0uMjkzLjI1OC0uMzUzLS4xNjMtNy4wMDItMy4yMS0uMjAxLS4wOTMtLjA5NC0uMi0xLjM4LTIuOTg4LS4wODEtLjE3Ny4wMzctLjE5TDExLjggNC42MzNsLjAyMy0uMTIxLjA3LS4xMDJaIi8+Cjwvc3ZnPgo=)\n *\n * @param {Object} props - Supabase icons props and any valid SVG attribute\n * @returns {JSX.Element} JSX Element\n *\n */\nconst Elastic = createSupabaseIcon(\n 'Elastic',\n [\n [\n 'path',\n {\n fill: '#0B64DD',\n stroke: '#fff',\n 'stroke-width': '1.2',\n d: 'M27.565 11.242c5.1 1.94 4.872 9.396-.245 11.127l-.162.055-.167-.039-5.28-1.238-.268-.063-.127-.244-1.4-2.69-.217-.417.352-.31 6.904-6.07.272-.24.338.13Z',\n key: 'el1',\n },\n ],\n [\n 'path',\n {\n fill: '#9ADC30',\n stroke: '#fff',\n 'stroke-width': '1.2',\n d: 'm22.047 21.239 4.8 1.125.316.074.11.304.066.19c.623 1.964-.26 3.78-1.652 4.797-1.434 1.048-3.51 1.32-5.182.022l-.29-.225.069-.361 1.037-5.454.117-.615.61.143Z',\n key: 'el2',\n },\n ],\n [\n 'path',\n {\n fill: '#1BA9F5',\n stroke: '#fff',\n 'stroke-width': '1.2',\n d: 'm5.01 9.63 5.267 1.255.283.067.122.264 1.235 2.65.187.4-.328.298-6.733 6.1-.272.248-.345-.132C1.939 19.83.72 17.456.752 15.153c.032-2.308 1.321-4.644 3.932-5.508l.162-.054.165.039Z',\n key: 'el3',\n },\n ],\n [\n 'path',\n {\n fill: '#F04E98',\n stroke: '#fff',\n 'stroke-width': '1.2',\n d: 'M6.281 4.32c1.416-1.08 3.48-1.387 5.222-.069l.297.226-.07.366-1.053 5.474-.118.615-.609-.144-4.8-1.137-.316-.075-.11-.306c-.709-1.967.149-3.876 1.557-4.95Z',\n key: 'el4',\n },\n ],\n [\n 'path',\n {\n fill: '#02BCB7',\n stroke: '#fff',\n 'stroke-width': '1.2',\n d: 'm12.466 14.433 7.03 3.211.188.086.095.183 1.555 2.985.096.184-.04.206-1.165 6.101-.024.122-.068.103c-2.68 3.958-6.902 4.71-10.268 3.26-3.356-1.447-5.834-5.07-5.126-9.736l.033-.21.158-.144 6.884-6.25.293-.265.36.164Z',\n key: 'el5',\n },\n ],\n [\n 'path',\n {\n fill: '#FEC514',\n stroke: '#fff',\n 'stroke-width': '1.2',\n d: 'M11.892 4.41C14.438.676 18.741.105 22.134 1.54c3.392 1.433 5.99 4.92 5.102 9.362l-.039.2-.153.133-7.066 6.213-.293.258-.353-.163-7.002-3.21-.201-.093-.094-.2-1.38-2.988-.081-.177.037-.19L11.8 4.633l.023-.121.07-.102Z',\n key: 'el6',\n },\n ],\n ],\n { fill: 'none', stroke: 'none', viewBox: '0 0 32 32' },\n);\n\nexport default Elastic;\n", + raw: "import createSupabaseIcon from '../createSupabaseIcon';\n\n/**\n * @component @name Elastic\n * @description Supabase SVG icon component, renders SVG Element with children.\n *\n * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgIHN0cm9rZT0iIzAwMCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDJweCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE5LjUgMTZMMTkuNjExOSAxNS45NjZDMjMuMTQ3OSAxNC44OTQ3IDIyLjgwNyAxMC4yODA0IDE5LjI4MjggOS4wNzk4M0wxOS4wNDkyIDlMMTguODYxMyA5LjE0ODUzTDE0LjI1MzUgMTMuMDQ1M00xOS41IDE2TDE5LjM4NDYgMTUuOTc1OUwxNiAxNS41TTE5LjUgMTZDMTkuOTI1MSAxNy4zMDQ2IDE5LjY5NDIgMTguODcwNyAxOC43NDQzIDE5LjU0NjNDMTcuNzY1NyAyMC4yNDI0IDE2LjM0OTEgMjAuNDIzMSAxNS4yMDgxIDE5LjU2MDlNOC4xMzMxNSA5LjE0NTFMOSAxMU00LjI3NTgyIDE1LjIxODdDMi43NDIzNCAxNC42MzEzIDEuOTkwNiAxMy4wMDM0IDIuMDEwMzMgMTEuNTc5NEMyLjAzMDA2IDEwLjE1MjMgMi42NjU3NyA4Ljg1NTk5IDQuMjc1NzEgOC4zMjE3Nk0xOC44Njg5IDkuMjE2ODhMMTguOTczNCA5LjEzMDI2TDE5IDlDMTkuNjA2NSA2LjEwNzAxIDE4LjIxMjEgMy45MDQ2NyAxNS44OTU0IDIuOTcxMzlDMTMuNTc4IDIuMDM2OCAxMC42MzkxIDIuNDA4NjggOC45MDAxNyA0Ljg0MDU2TDguODUzMDUgNC45MDY5OUw4LjgzNzM0IDQuOTg1OE01LjI0NTUyIDQuNzYyMTZDNi4yMTE0MyA0LjA0NzA1IDcuNjE5MzYgMy44NDM3NyA4LjgwNzY1IDQuNzE2NDdMOS4wMTAyNCA0Ljg2NjEyTDguOTYyNDkgNS4xMDg0Nkw4LjE2MzcxIDkuMTQwMjZMNC4yNTg0NyA4LjI0MjM5TDQuMTgzNDMgOC4wMzk3OEMzLjY5OTggNi43MzczNCA0LjI4NTA3IDUuNDczMyA1LjI0NTUyIDQuNzYyMTZaTTE0Ljc2NCAxMy4zNDIzTDE0LjgzMTggMTMuNDU4OEwxNi4wMTAyIDE1LjQ3NUwxNS45ODE3IDE1LjYwNjFMMTUuMTUwMiAxOS40ODc5TDE1LjEzMyAxOS41NjU1TDE1LjA4NDUgMTkuNjMxQzEzLjE3MTYgMjIuMTQ5MyAxMC4xNTgxIDIyLjYyNzggNy43NTU2MSAyMS43MDUyQzUuMzYwMjMgMjAuNzg0NSAzLjU5MTUzIDE4LjQ3OTQgNC4wOTY4NyAxNS41MTA3TDQuMTIwNDMgMTUuMzc3TDQuMjMzMiAxNS4yODU0TDkuMTQ2NzMgMTEuMzA4OUw5LjM1NTg2IDExLjE0MDNMMTQuNzY0IDEzLjM0MjNaIi8+Cjwvc3ZnPgo=)\n *\n * @param {Object} props - Supabase icons props and any valid SVG attribute\n * @returns {JSX.Element} JSX Element\n *\n */\nconst Elastic = createSupabaseIcon(\n 'Elastic',\n [\n [\n 'path',\n {\n d: 'M19.5 16L19.6119 15.966C23.1479 14.8947 22.807 10.2804 19.2828 9.07983L19.0492 9L18.8613 9.14853L14.2535 13.0453M19.5 16L19.3846 15.9759L16 15.5M19.5 16C19.9251 17.3046 19.6942 18.8707 18.7443 19.5463C17.7657 20.2424 16.3491 20.4231 15.2081 19.5609M8.13315 9.1451L9 11M4.27582 15.2187C2.74234 14.6313 1.9906 13.0034 2.01033 11.5794C2.03006 10.1523 2.66577 8.85599 4.27571 8.32176M18.8689 9.21688L18.9734 9.13026L19 9C19.6065 6.10701 18.2121 3.90467 15.8954 2.97139C13.578 2.0368 10.6391 2.40868 8.90017 4.84056L8.85305 4.90699L8.83734 4.9858M5.24552 4.76216C6.21143 4.04705 7.61936 3.84377 8.80765 4.71647L9.01024 4.86612L8.96249 5.10846L8.16371 9.14026L4.25847 8.24239L4.18343 8.03978C3.6998 6.73734 4.28507 5.4733 5.24552 4.76216ZM14.764 13.3423L14.8318 13.4588L16.0102 15.475L15.9817 15.6061L15.1502 19.4879L15.133 19.5655L15.0845 19.631C13.1716 22.1493 10.1581 22.6278 7.75561 21.7052C5.36023 20.7845 3.59153 18.4794 4.09687 15.5107L4.12043 15.377L4.2332 15.2854L9.14673 11.3089L9.35586 11.1403L14.764 13.3423Z',\n key: '1enzsi',\n },\n ],\n ],\n { fill: 'none', stroke: 'currentColor', strokeWidth: '1.5' },\n);\n\nexport default Elastic;\n", component: React.lazy(() => import('icons/src/icons/elastic')), import: "import { Elastic } from 'icons'", - svg: "\n\n\n\n\n\n\n\n", + svg: "\n\n\n", jsx: "import { Elastic } from \"icons\"\n \n " }, { diff --git a/packages/icons/src/icons/compute.ts b/packages/icons/src/icons/compute.ts index d02beb9714448..14c29acdab702 100644 --- a/packages/icons/src/icons/compute.ts +++ b/packages/icons/src/icons/compute.ts @@ -4,7 +4,7 @@ import createSupabaseIcon from '../createSupabaseIcon'; * @component @name Compute * @description Supabase SVG icon component, renders SVG Element with children. * - * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjMiIHZpZXdCb3g9IjAgMCAyNCAyMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBzdHJva2U9IiMwMDAiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOyBib3JkZXItcmFkaXVzOiAycHgiIHN0cm9rZS13aWR0aD0iMS41Ij48cGF0aCBkPSJNNy41NTU1NiA4LjcwMDQ5TDEyIDExLjUwMTFNMTIgMTEuNTAxMUwxNi40NDQ0IDguNzAwNDlNMTIgMTEuNTAxMUwxMiAxNi40MDFNMjIgMTUuNzAwOVY3LjI5OTE0QzIxLjk5OTYgNi45MzA4IDIxLjg5NjcgNi41NjkwNCAyMS43MDE3IDYuMjUwMTRDMjEuNTA2NyA1LjkzMTI1IDIxLjIyNjQgNS42NjY0MyAyMC44ODg5IDUuNDgyMjdMMTMuMTExMSAxLjI4MTRDMTIuNzczMyAxLjA5NzA1IDEyLjM5MDEgMSAxMiAxQzExLjYwOTkgMSAxMS4yMjY3IDEuMDk3MDUgMTAuODg4OSAxLjI4MTRMMy4xMTExMSA1LjQ4MjI3QzIuNzczNjMgNS42NjY0MyAyLjQ5MzMxIDUuOTMxMjUgMi4yOTgyOSA2LjI1MDE0QzIuMTAzMjcgNi41NjkwNCAyLjAwMDQgNi45MzA4IDIgNy4yOTkxNFYxNS43MDA5QzIuMDAwNCAxNi4wNjkyIDIuMTAzMjcgMTYuNDMxIDIuMjk4MjkgMTYuNzQ5OUMyLjQ5MzMxIDE3LjA2ODggMi43NzM2MyAxNy4zMzM2IDMuMTExMTEgMTcuNTE3N0wxMC44ODg5IDIxLjcxODZDMTEuMjI2NyAyMS45MDI5IDExLjYwOTkgMjIgMTIgMjJDMTIuMzkwMSAyMiAxMi43NzMzIDIxLjkwMjkgMTMuMTExMSAyMS43MTg2TDIwLjg4ODkgMTcuNTE3N0MyMS4yMjY0IDE3LjMzMzYgMjEuNTA2NyAxNy4wNjg4IDIxLjcwMTcgMTYuNzQ5OUMyMS44OTY3IDE2LjQzMSAyMS45OTk2IDE2LjA2OTIgMjIgMTUuNzAwOVpNMTcuOTI1OSAxMy43NDJWOS4yNjAxNUMxNy45MjU3IDkuMDYzNjYgMTcuODY0NyA4Ljg3MDY4IDE3Ljc0OTIgOC43MDA1N0MxNy42MzM2IDguNTMwNDYgMTcuNDY3NSA4LjM4OTE5IDE3LjI2NzUgOC4yOTA5NUwxMi42NTg0IDYuMDUwMDNDMTIuNDU4MiA1Ljk1MTY5IDEyLjIzMTIgNS44OTk5MiAxMiA1Ljg5OTkyQzExLjc2ODggNS44OTk5MiAxMS41NDE4IDUuOTUxNjkgMTEuMzQxNiA2LjA1MDAzTDYuNzMyNTEgOC4yOTA5NUM2LjUzMjUyIDguMzg5MTkgNi4zNjY0MSA4LjUzMDQ2IDYuMjUwODQgOC43MDA1N0M2LjEzNTI3IDguODcwNjggNi4wNzQzMSA5LjA2MzY2IDYuMDc0MDcgOS4yNjAxNVYxMy43NDJDNi4wNzQzMSAxMy45Mzg1IDYuMTM1MjcgMTQuMTMxNSA2LjI1MDg0IDE0LjMwMTZDNi4zNjY0MSAxNC40NzE3IDYuNTMyNTIgMTQuNjEyOSA2LjczMjUxIDE0LjcxMTJMMTEuMzQxNiAxNi45NTIxQzExLjU0MTggMTcuMDUwNCAxMS43Njg4IDE3LjEwMjIgMTIgMTcuMTAyMkMxMi4yMzEyIDE3LjEwMjIgMTIuNDU4MiAxNy4wNTA0IDEyLjY1ODQgMTYuOTUyMUwxNy4yNjc1IDE0LjcxMTJDMTcuNDY3NSAxNC42MTI5IDE3LjYzMzYgMTQuNDcxNyAxNy43NDkyIDE0LjMwMTZDMTcuODY0NyAxNC4xMzE1IDE3LjkyNTcgMTMuOTM4NSAxNy45MjU5IDEzLjc0MloiLz4KPC9zdmc+Cg==) + * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDJweCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuNSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMTIgMTIgNS4zMDktMi41NDdhMS4xNCAxLjE0IDAgMCAwLS40NDItLjM4bC00LjIzMS0yLjIzMUExLjUgMS41IDAgMCAwIDEyIDYuNzAzYTEuMiAxLjIgMCAwIDAtLjU3MS4xMzlMNy4yMTIgOS4wNzRjLS4xODMuMDktLjMzNS4yMjItLjQ0LjM4TTEyIDEyIDYuNzcyIDkuNDUzTTEyIDEydjUuNE02Ljc3MiA5LjQ1M3EwIDAgMCAwbTEwLjUzNy4wMDFhLjkuOSAwIDAgMSAuMTYyLjUxNnY0LjE0MWMwIC4xODItLjA1Ni4zNi0uMTYyLjUxOGExLjE0IDEuMTQgMCAwIDEtLjQ0Mi4zNzhsLTQuMjI0IDIuMjMyYy0uMi4wOTgtLjQyLjE1My0uNjQzLjE2MS0uMjEyIDAtLjM4LS4wNy0uNTYzLS4xNjFsLTQuMjI1LTIuMjMyYTEuMTQgMS4xNCAwIDAgMS0uNDQxLS4zNzguOTMuOTMgMCAwIDEtLjE2Mi0uNTE4VjkuOTdhLjkuOSAwIDAgMSAuMTYzLS41MTdtNC4zNzYtNi44MTNhMiAyIDAgMCAxIDEuOTI3IDBsNy4wOTMgMy45YTIgMiAwIDAgMSAxLjAzNiAxLjc1M3Y3LjYzNWEyIDIgMCAwIDEtMS4wMzYgMS43NTJsLTcuMDkzIDMuOWEyIDIgMCAwIDEtMS45MjcgMGwtNy4wOTQtMy45YTIgMiAwIDAgMS0xLjAzNi0xLjc1MlY4LjI5M0EyIDIgMCAwIDEgNC4wNTQgNi41NHoiLz4KPC9zdmc+) * * @param {Object} props - Supabase icons props and any valid SVG attribute * @returns {JSX.Element} JSX Element @@ -16,12 +16,18 @@ const Compute = createSupabaseIcon( [ 'path', { - d: 'M7.55556 8.70049L12 11.5011M12 11.5011L16.4444 8.70049M12 11.5011L12 16.401M22 15.7009V7.29914C21.9996 6.9308 21.8967 6.56904 21.7017 6.25014C21.5067 5.93125 21.2264 5.66643 20.8889 5.48227L13.1111 1.2814C12.7733 1.09705 12.3901 1 12 1C11.6099 1 11.2267 1.09705 10.8889 1.2814L3.11111 5.48227C2.77363 5.66643 2.49331 5.93125 2.29829 6.25014C2.10327 6.56904 2.0004 6.9308 2 7.29914V15.7009C2.0004 16.0692 2.10327 16.431 2.29829 16.7499C2.49331 17.0688 2.77363 17.3336 3.11111 17.5177L10.8889 21.7186C11.2267 21.9029 11.6099 22 12 22C12.3901 22 12.7733 21.9029 13.1111 21.7186L20.8889 17.5177C21.2264 17.3336 21.5067 17.0688 21.7017 16.7499C21.8967 16.431 21.9996 16.0692 22 15.7009ZM17.9259 13.742V9.26015C17.9257 9.06366 17.8647 8.87068 17.7492 8.70057C17.6336 8.53046 17.4675 8.38919 17.2675 8.29095L12.6584 6.05003C12.4582 5.95169 12.2312 5.89992 12 5.89992C11.7688 5.89992 11.5418 5.95169 11.3416 6.05003L6.73251 8.29095C6.53252 8.38919 6.36641 8.53046 6.25084 8.70057C6.13527 8.87068 6.07431 9.06366 6.07407 9.26015V13.742C6.07431 13.9385 6.13527 14.1315 6.25084 14.3016C6.36641 14.4717 6.53252 14.6129 6.73251 14.7112L11.3416 16.9521C11.5418 17.0504 11.7688 17.1022 12 17.1022C12.2312 17.1022 12.4582 17.0504 12.6584 16.9521L17.2675 14.7112C17.4675 14.6129 17.6336 14.4717 17.7492 14.3016C17.8647 14.1315 17.9257 13.9385 17.9259 13.742Z', - key: '1cro8t', + d: 'm12 12 5.309-2.547a1.14 1.14 0 0 0-.442-.38l-4.231-2.231A1.5 1.5 0 0 0 12 6.703a1.2 1.2 0 0 0-.571.139L7.212 9.074c-.183.09-.335.222-.44.38M12 12 6.772 9.453M12 12v5.4M6.772 9.453q0 0 0 0m10.537.001a.9.9 0 0 1 .162.516v4.141c0 .182-.056.36-.162.518a1.14 1.14 0 0 1-.442.378l-4.224 2.232c-.2.098-.42.153-.643.161-.212 0-.38-.07-.563-.161l-4.225-2.232a1.14 1.14 0 0 1-.441-.378.93.93 0 0 1-.162-.518V9.97a.9.9 0 0 1 .163-.517m4.376-6.813a2 2 0 0 1 1.927 0l7.093 3.9a2 2 0 0 1 1.036 1.753v7.635a2 2 0 0 1-1.036 1.752l-7.093 3.9a2 2 0 0 1-1.927 0l-7.094-3.9a2 2 0 0 1-1.036-1.752V8.293A2 2 0 0 1 4.054 6.54z', + key: 'afg6tn', }, ], ], - { fill: 'none', stroke: 'currentColor', strokeWidth: '1.5' }, + { + fill: 'none', + stroke: 'currentColor', + strokeWidth: '1.5', + strokeLinecap: 'round', + strokeLinejoin: 'round', + }, ); export default Compute; diff --git a/packages/icons/src/icons/elastic.ts b/packages/icons/src/icons/elastic.ts index 3363932c4e4a3..2405c06112681 100644 --- a/packages/icons/src/icons/elastic.ts +++ b/packages/icons/src/icons/elastic.ts @@ -4,7 +4,7 @@ import createSupabaseIcon from '../createSupabaseIcon'; * @component @name Elastic * @description Supabase SVG icon component, renders SVG Element with children. * - * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBmaWxsPSIjMEI2NEREIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS4yIiBkPSJNMjcuNTY1IDExLjI0MmM1LjEgMS45NCA0Ljg3MiA5LjM5Ni0uMjQ1IDExLjEyN2wtLjE2Mi4wNTUtLjE2Ny0uMDM5LTUuMjgtMS4yMzgtLjI2OC0uMDYzLS4xMjctLjI0NC0xLjQtMi42OS0uMjE3LS40MTcuMzUyLS4zMSA2LjkwNC02LjA3LjI3Mi0uMjQuMzM4LjEzWiIvPgo8cGF0aCBmaWxsPSIjOUFEQzMwIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS4yIiBkPSJtMjIuMDQ3IDIxLjIzOSA0LjggMS4xMjUuMzE2LjA3NC4xMS4zMDQuMDY2LjE5Yy42MjMgMS45NjQtLjI2IDMuNzgtMS42NTIgNC43OTctMS40MzQgMS4wNDgtMy41MSAxLjMyLTUuMTgyLjAyMmwtLjI5LS4yMjUuMDY5LS4zNjEgMS4wMzctNS40NTQuMTE3LS42MTUuNjEuMTQzWiIvPgo8cGF0aCBmaWxsPSIjMUJBOUY1IiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS4yIiBkPSJtNS4wMSA5LjYzIDUuMjY3IDEuMjU1LjI4My4wNjcuMTIyLjI2NCAxLjIzNSAyLjY1LjE4Ny40LS4zMjguMjk4LTYuNzMzIDYuMS0uMjcyLjI0OC0uMzQ1LS4xMzJDMS45MzkgMTkuODMuNzIgMTcuNDU2Ljc1MiAxNS4xNTNjLjAzMi0yLjMwOCAxLjMyMS00LjY0NCAzLjkzMi01LjUwOGwuMTYyLS4wNTQuMTY1LjAzOVoiLz4KPHBhdGggZmlsbD0iI0YwNEU5OCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuMiIgZD0iTTYuMjgxIDQuMzJjMS40MTYtMS4wOCAzLjQ4LTEuMzg3IDUuMjIyLS4wNjlsLjI5Ny4yMjYtLjA3LjM2Ni0xLjA1MyA1LjQ3NC0uMTE4LjYxNS0uNjA5LS4xNDQtNC44LTEuMTM3LS4zMTYtLjA3NS0uMTEtLjMwNmMtLjcwOS0xLjk2Ny4xNDktMy44NzYgMS41NTctNC45NVoiLz4KPHBhdGggZmlsbD0iIzAyQkNCNyIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuMiIgZD0ibTEyLjQ2NiAxNC40MzMgNy4wMyAzLjIxMS4xODguMDg2LjA5NS4xODMgMS41NTUgMi45ODUuMDk2LjE4NC0uMDQuMjA2LTEuMTY1IDYuMTAxLS4wMjQuMTIyLS4wNjguMTAzYy0yLjY4IDMuOTU4LTYuOTAyIDQuNzEtMTAuMjY4IDMuMjYtMy4zNTYtMS40NDctNS44MzQtNS4wNy01LjEyNi05LjczNmwuMDMzLS4yMS4xNTgtLjE0NCA2Ljg4NC02LjI1LjI5My0uMjY1LjM2LjE2NFoiLz4KPHBhdGggZmlsbD0iI0ZFQzUxNCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEuMiIgZD0iTTExLjg5MiA0LjQxQzE0LjQzOC42NzYgMTguNzQxLjEwNSAyMi4xMzQgMS41NGMzLjM5MiAxLjQzMyA1Ljk5IDQuOTIgNS4xMDIgOS4zNjJsLS4wMzkuMi0uMTUzLjEzMy03LjA2NiA2LjIxMy0uMjkzLjI1OC0uMzUzLS4xNjMtNy4wMDItMy4yMS0uMjAxLS4wOTMtLjA5NC0uMi0xLjM4LTIuOTg4LS4wODEtLjE3Ny4wMzctLjE5TDExLjggNC42MzNsLjAyMy0uMTIxLjA3LS4xMDJaIi8+Cjwvc3ZnPgo=) + * @preview ![img](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgIHN0cm9rZT0iIzAwMCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDJweCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE5LjUgMTZMMTkuNjExOSAxNS45NjZDMjMuMTQ3OSAxNC44OTQ3IDIyLjgwNyAxMC4yODA0IDE5LjI4MjggOS4wNzk4M0wxOS4wNDkyIDlMMTguODYxMyA5LjE0ODUzTDE0LjI1MzUgMTMuMDQ1M00xOS41IDE2TDE5LjM4NDYgMTUuOTc1OUwxNiAxNS41TTE5LjUgMTZDMTkuOTI1MSAxNy4zMDQ2IDE5LjY5NDIgMTguODcwNyAxOC43NDQzIDE5LjU0NjNDMTcuNzY1NyAyMC4yNDI0IDE2LjM0OTEgMjAuNDIzMSAxNS4yMDgxIDE5LjU2MDlNOC4xMzMxNSA5LjE0NTFMOSAxMU00LjI3NTgyIDE1LjIxODdDMi43NDIzNCAxNC42MzEzIDEuOTkwNiAxMy4wMDM0IDIuMDEwMzMgMTEuNTc5NEMyLjAzMDA2IDEwLjE1MjMgMi42NjU3NyA4Ljg1NTk5IDQuMjc1NzEgOC4zMjE3Nk0xOC44Njg5IDkuMjE2ODhMMTguOTczNCA5LjEzMDI2TDE5IDlDMTkuNjA2NSA2LjEwNzAxIDE4LjIxMjEgMy45MDQ2NyAxNS44OTU0IDIuOTcxMzlDMTMuNTc4IDIuMDM2OCAxMC42MzkxIDIuNDA4NjggOC45MDAxNyA0Ljg0MDU2TDguODUzMDUgNC45MDY5OUw4LjgzNzM0IDQuOTg1OE01LjI0NTUyIDQuNzYyMTZDNi4yMTE0MyA0LjA0NzA1IDcuNjE5MzYgMy44NDM3NyA4LjgwNzY1IDQuNzE2NDdMOS4wMTAyNCA0Ljg2NjEyTDguOTYyNDkgNS4xMDg0Nkw4LjE2MzcxIDkuMTQwMjZMNC4yNTg0NyA4LjI0MjM5TDQuMTgzNDMgOC4wMzk3OEMzLjY5OTggNi43MzczNCA0LjI4NTA3IDUuNDczMyA1LjI0NTUyIDQuNzYyMTZaTTE0Ljc2NCAxMy4zNDIzTDE0LjgzMTggMTMuNDU4OEwxNi4wMTAyIDE1LjQ3NUwxNS45ODE3IDE1LjYwNjFMMTUuMTUwMiAxOS40ODc5TDE1LjEzMyAxOS41NjU1TDE1LjA4NDUgMTkuNjMxQzEzLjE3MTYgMjIuMTQ5MyAxMC4xNTgxIDIyLjYyNzggNy43NTU2MSAyMS43MDUyQzUuMzYwMjMgMjAuNzg0NSAzLjU5MTUzIDE4LjQ3OTQgNC4wOTY4NyAxNS41MTA3TDQuMTIwNDMgMTUuMzc3TDQuMjMzMiAxNS4yODU0TDkuMTQ2NzMgMTEuMzA4OUw5LjM1NTg2IDExLjE0MDNMMTQuNzY0IDEzLjM0MjNaIi8+Cjwvc3ZnPgo=) * * @param {Object} props - Supabase icons props and any valid SVG attribute * @returns {JSX.Element} JSX Element @@ -16,65 +16,12 @@ const Elastic = createSupabaseIcon( [ 'path', { - fill: '#0B64DD', - stroke: '#fff', - 'stroke-width': '1.2', - d: 'M27.565 11.242c5.1 1.94 4.872 9.396-.245 11.127l-.162.055-.167-.039-5.28-1.238-.268-.063-.127-.244-1.4-2.69-.217-.417.352-.31 6.904-6.07.272-.24.338.13Z', - key: 'el1', - }, - ], - [ - 'path', - { - fill: '#9ADC30', - stroke: '#fff', - 'stroke-width': '1.2', - d: 'm22.047 21.239 4.8 1.125.316.074.11.304.066.19c.623 1.964-.26 3.78-1.652 4.797-1.434 1.048-3.51 1.32-5.182.022l-.29-.225.069-.361 1.037-5.454.117-.615.61.143Z', - key: 'el2', - }, - ], - [ - 'path', - { - fill: '#1BA9F5', - stroke: '#fff', - 'stroke-width': '1.2', - d: 'm5.01 9.63 5.267 1.255.283.067.122.264 1.235 2.65.187.4-.328.298-6.733 6.1-.272.248-.345-.132C1.939 19.83.72 17.456.752 15.153c.032-2.308 1.321-4.644 3.932-5.508l.162-.054.165.039Z', - key: 'el3', - }, - ], - [ - 'path', - { - fill: '#F04E98', - stroke: '#fff', - 'stroke-width': '1.2', - d: 'M6.281 4.32c1.416-1.08 3.48-1.387 5.222-.069l.297.226-.07.366-1.053 5.474-.118.615-.609-.144-4.8-1.137-.316-.075-.11-.306c-.709-1.967.149-3.876 1.557-4.95Z', - key: 'el4', - }, - ], - [ - 'path', - { - fill: '#02BCB7', - stroke: '#fff', - 'stroke-width': '1.2', - d: 'm12.466 14.433 7.03 3.211.188.086.095.183 1.555 2.985.096.184-.04.206-1.165 6.101-.024.122-.068.103c-2.68 3.958-6.902 4.71-10.268 3.26-3.356-1.447-5.834-5.07-5.126-9.736l.033-.21.158-.144 6.884-6.25.293-.265.36.164Z', - key: 'el5', - }, - ], - [ - 'path', - { - fill: '#FEC514', - stroke: '#fff', - 'stroke-width': '1.2', - d: 'M11.892 4.41C14.438.676 18.741.105 22.134 1.54c3.392 1.433 5.99 4.92 5.102 9.362l-.039.2-.153.133-7.066 6.213-.293.258-.353-.163-7.002-3.21-.201-.093-.094-.2-1.38-2.988-.081-.177.037-.19L11.8 4.633l.023-.121.07-.102Z', - key: 'el6', + d: 'M19.5 16L19.6119 15.966C23.1479 14.8947 22.807 10.2804 19.2828 9.07983L19.0492 9L18.8613 9.14853L14.2535 13.0453M19.5 16L19.3846 15.9759L16 15.5M19.5 16C19.9251 17.3046 19.6942 18.8707 18.7443 19.5463C17.7657 20.2424 16.3491 20.4231 15.2081 19.5609M8.13315 9.1451L9 11M4.27582 15.2187C2.74234 14.6313 1.9906 13.0034 2.01033 11.5794C2.03006 10.1523 2.66577 8.85599 4.27571 8.32176M18.8689 9.21688L18.9734 9.13026L19 9C19.6065 6.10701 18.2121 3.90467 15.8954 2.97139C13.578 2.0368 10.6391 2.40868 8.90017 4.84056L8.85305 4.90699L8.83734 4.9858M5.24552 4.76216C6.21143 4.04705 7.61936 3.84377 8.80765 4.71647L9.01024 4.86612L8.96249 5.10846L8.16371 9.14026L4.25847 8.24239L4.18343 8.03978C3.6998 6.73734 4.28507 5.4733 5.24552 4.76216ZM14.764 13.3423L14.8318 13.4588L16.0102 15.475L15.9817 15.6061L15.1502 19.4879L15.133 19.5655L15.0845 19.631C13.1716 22.1493 10.1581 22.6278 7.75561 21.7052C5.36023 20.7845 3.59153 18.4794 4.09687 15.5107L4.12043 15.377L4.2332 15.2854L9.14673 11.3089L9.35586 11.1403L14.764 13.3423Z', + key: '1enzsi', }, ], ], - { fill: 'none', stroke: 'none', viewBox: '0 0 32 32' }, + { fill: 'none', stroke: 'currentColor', strokeWidth: '1.5' }, ); export default Elastic; diff --git a/packages/icons/src/raw-icons/compute.svg b/packages/icons/src/raw-icons/compute.svg index f185e4918efd5..b4c18afc61626 100644 --- a/packages/icons/src/raw-icons/compute.svg +++ b/packages/icons/src/raw-icons/compute.svg @@ -1,3 +1,3 @@ - - + + diff --git a/packages/icons/src/raw-icons/elastic.svg b/packages/icons/src/raw-icons/elastic.svg index d104b19fb9310..e992b21348a61 100644 --- a/packages/icons/src/raw-icons/elastic.svg +++ b/packages/icons/src/raw-icons/elastic.svg @@ -1,8 +1,3 @@ - - - - - - - + +