- Split "WCAG:" into its own font-medium text-foreground span, matching
"Headline:" (previously the whole segment, including the label, was
colored by pass/fail state)
- Preview row: w-full -> w-[65%], with the wrapper's horizontal centering
made unconditional (was @4xl-only) so the narrower row stays centered in
both stacked and side-by-side layouts. Verified via getBoundingClientRect:
exactly 65.0% of the canvas's usable width, with equal left/right margins
(225.4px each) at 1700px.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous round's items-start fix aligned the two cards relative to EACH
OTHER (proven equal, but only proves top===center===bottom trivially) — it
never addressed the real complaint: the whole OG+Thumb row sat pinned near
the top of the canvas with dead space below on wide/tall screens. Wrapped the
row in a flex-1 container that centers it (@4xl:items-center
@4xl:justify-center) within the space remaining below the view toggle. Since
flex items don't shrink below their content size, an overflowing row still
just grows the wrapper rather than clipping (avoids the classic
flex-center-clips-on-overflow bug). Verified via getBoundingClientRect: 186.1px
of space above and below the row, exactly equal.
OG info panel: prefixed "Headline:" at the start, moved the fit-mode
indicator to right after the font size and reworded it to "(auto on)" (shown
only when auto-fit is active), and prefixed "WCAG:" before the contrast
score. Reads as "Headline: 64px (auto on) · 2 lines · WCAG: 16.3:1 AAA".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Panel is now position:absolute (not a flex sibling) over a full-bleed
absolute-inset canvas — one continuous dot-grid surface behind it, instead
of two boxes split by a shared flex edge. Canvas content gets pr-[380px]
so it doesn't render under the panel.
- Preview row: items-center -> items-start. OG and Thumb render at identical
size (same aspect ratio, equal flex-basis) with identical-height label rows
above them, so top-aligning is what actually keeps the IMAGES aligned;
items-center was aligning the outer card blocks (image + variable-height
caption below), which visibly offset the images from each other. Verified
via computed styles: both cards now start at the exact same top regardless
of caption height.
- Removed the zoom control entirely (state, buttons, ZOOM_MIN/MAX, the
style={{zoom}} CSS zoom) — it scaled the whole card including buttons/
labels, not just the rendered image.
- OG fit-info pill: added a real border + solid background (was
bg-surface-200 with no border, blending into the canvas) so it reads as a
distinct panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Package the top bar (title + Export @2x) into the sidebar itself, as a
sticky header strip inside the panel rather than a separate full-width bar
- Move the panel from the left to the right, and restyle it as a floating
card (rounded corners, shadow, inset margin) over the canvas instead of a
full-height docked sidebar
- Preview row: add @4xl:items-center so the OG and Thumb cards align by
vertical center when shown side by side on wide screens (they already
stacked at medium widths); verified via computed styles (align-items:
center, cards offset by exactly half their height difference)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spreading the result straight onto <img> as JSX props meant satori saw
w={..} h={..} instead of width/height — no intrinsic size, so the logo
filled its entire container instead of fitting a small box. Caught via a
real end-to-end upload test against the live Supabase project (secret key +
migrations now applied): a color logo rendered full-canvas instead of boxed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Line icons are stroke-only by brand rule, but a partner's logo (Grafana,
Multigres, etc.) needs to render in ITS OWN colors — a second asset kind
alongside the existing icon system, not a replacement for it.
- supabase/migrations/0002_logo_assets.sql: add width/height to `assets`
(kind + storage_path already existed from 0001)
- lib/assets/sanitize-svg.ts: sanitizeLogoSvg() — a separate allowlist that
PRESERVES fill/stroke/gradients/style color declarations (unlike the icon
sanitizer, which strips them for the stroke-only treatment), while still
blocking scripts, event handlers, and external references (internal `#id`
refs are kept so gradient defs/<use> keep working). Unit-tested against a
mixed color+attack payload.
- lib/supabase/assets.ts: insertLogoAsset() uploads to the og-assets Storage
bucket (raster-safe, unlike inline SVG bodies) and records client-measured
width/height; rowToIcon exposes kind/url/width/height for both kinds.
- app/api/assets/route.ts: POST branches on a `kind` field ('icon', default,
or 'logo'); logo accepts SVG/PNG/JPEG/WebP up to 2MB, requires width+height.
- app/api/og/route.tsx: icon slots render logos as-is (no stroke
normalization), fit to their natural aspect ratio via a new fitBox() helper.
- app/page.tsx: a second "+ Upload logo (color)" control measures the file's
natural dimensions in-browser (Image().onload) before POSTing; the icon
picker renders logo entries as real <img> (actual colors) instead of the
forced-stroke SVG redraw.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop a plain figma-links.txt (one URL per line) instead of pairing every
image with a .figma.txt file — Claude Code pulls the screenshot/design
context per link directly via the Figma integration. Also notes this same
drop zone doubles as the intake for a future image-generation training set.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Move Font size out of Layout, under Headline in Content (auto-fit toggle +
slider now sit right below the headline field)
- Rename the sentence-case checkbox to "Disable sentence-case" and invert it
(unchecked = sentence-case on, the existing default) so its label matches
its behavior
- Background: remove the color picker — patterns are white-only now
(lib/ai/claude.ts + the Suggestion validators updated to match). Opacity
range raised from 4-12% to 20-35% (baseline 20%), and the single source of
truth (lib/design/patterns.ts PATTERN_OPACITY) drives both the editor slider
and the /api/og server clamp
- Scale options: drop Small, rename Medium -> "Default" and Large -> "Bigger"
- Bump template/example defaultPattern.opacity from ~5% to 20% to match the
new range; /api/og keeps accepting legacy sm/green query params so
previously-generated URLs keep rendering unchanged
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removes DialRoot from the root layout, the useDialKit "Render tuning" controls
from the editor, and the iconScale/strokePx tuning params from /api/og; uninstalls
dialkit + motion. The Ollama engine and expanded icon library are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two ways to keep smart art direction without a paid AI service:
B) Local LLM engine — lib/ai/ollama.ts calls a self-hosted Ollama server
(opt-in via OLLAMA_URL), reusing the exact prompt + validation as the Claude
engine. Nothing leaves your infra. /api/suggest engine order is now
Claude -> Ollama -> keyword. Shared helpers exported from claude.ts.
A) Bigger on-brand palette — scripts/gen-lucide-icons.mjs generates a curated
30-icon Lucide set (lib/assets/lucide-icons.ts) from the lucide-static dev
dep, with Supabase-flavored tags. lib/assets/icon-library.ts combines seed +
Lucide into ICON_LIBRARY/ICON_MAP; the picker, keyword matcher, LLM catalog,
and /api/og icon resolver all use it, so a text prompt matches ~35 icons
(semantic reasoning comes free once the local LLM is on).
Docs: .env.local.example + README (Ollama option). App still runs with no AI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /api/og: accept optional iconScale + strokePx params (backward-compatible,
clamped), applied to both the OG and Thumb icon rendering
- Editor: a dev-only useDialKit "Render tuning" panel (iconScale, strokePx)
feeds live values into the render URLs; defaults (1, 2) leave the URL
untouched so production output is unchanged
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Install dialkit ^1.3.0 + motion ^12.42.0
- Import dialkit/styles.css and mount <DialRoot/> as a sibling of {children} in
the root layout (not wrapping it), gated to development so the tuning panel
never ships to end users. Controls appear once components register dials via
useDialKit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- lib/assets/sanitize-svg.ts: conservative allowlist sanitizer → {viewBox, body}
for line-art SVGs; strips script/foreignObject/image/event-handlers/hrefs and
rejects DOCTYPE/entity and non-drawing input
- lib/supabase/assets.ts: listAssets() + resolveIcon() (seed → DB) via the anon
client; insertAsset() via the admin (secret) client
- app/api/assets/route.ts: GET the library; POST an SVG (multipart) → sanitize →
insert. Returns a clear 503 when SUPABASE_SECRET_KEY isn't configured
- /api/og: resolve the icon param through resolveIcon so uploaded assets render
in OG + Thumb
- Editor Assets panel: load /api/assets, merge seed + uploaded in the grid, and
replace the disabled button with a real file upload (loading + error states)
Reads work with the publishable key; uploads (writes) go through our server
route with the secret key, keeping the publishable key read-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add @supabase/supabase-js (catalog) + lib/supabase/server.ts: anon
(publishable, RLS-respecting reads) + admin (secret) clients that return null
and no-op gracefully when the project isn't configured
- Add supabase/migrations/0001_init.sql: featured_examples / assets / posts
tables, RLS (public read on the reference tables), the og-assets Storage
bucket, and a seed of the featured-examples corpus. Idempotent.
- Read featured_examples from the DB when configured
(lib/supabase/featured-examples.ts), falling back to the bundled corpus on
missing table / query error / no config; thread the examples list through
suggestArtDirection + suggestWithClaude + /api/suggest
- Document Supabase env vars (.env.local.example) + a README setup section
The app still runs fully with no backend — Supabase is opt-in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the keyword-only "Describe this post" suggester with a real Claude
call, grounded in a curated featured-examples corpus.
- Add featured-examples corpus (lib/ai/examples.ts) as approved precedent, and
match against it first in the keyword suggester
- Add Claude suggester (lib/ai/claude.ts) + Node-runtime /api/suggest route:
claude-opus-4-8 reasons over the icon/template/pattern vocab + examples with
structured JSON output; every field is validated back into the allowed set;
falls back to the keyword matcher when ANTHROPIC_API_KEY is unset or on any
error, so the route always returns a valid suggestion
- Editor: Generate now calls the route (async + "Generating…" state), adds the
"ai" source with a "✨ AI suggestion" label; drop the client-side suggester
- Add "Save current as example" to grow the corpus
- Pin @anthropic-ai/sdk ^0.106.0; add .env.local.example + README AI section
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Headline weight 600→500; balanced 2-line wrapping (no full-width span) plus
widow/orphan avoidance in the fit algorithm
- Grid-snap the background pattern to each template's content anchor on both axes
- Eyebrow "pill" style option alongside plain text
- Reorder sidebar (lead with AI → Layout → Assets → Content) with clearer
section-header vs. label hierarchy; Plain/Pill eyebrow control; disabled
"Upload SVG" affordance noting it lands with the Supabase asset library
- Condense the OG spec readout to a single-line info panel
- Both view: responsive side-by-side that fills the canvas on wide screens and
stacks when narrow, plus canvas zoom
- Fix a horizontal scroll on the font-size slider
- AI art direction: explicit "Generate" button (sparkle icon) instead of live
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- App chrome switches to light mode (the generated image stays dark by spec)
- Canvas gets a dot-grid workspace background
- Move the OG/Thumb view toggle onto the canvas; add a default "Both" view that
renders OG + Thumb together, each with its own Copy URL / Download
- Illustrations default to a neutral 2px / #A0A0A0 stroke (eyebrow stays green)
- Add a lightweight, backend-free AI art-direction field: describe the post ->
suggested icon + template + rationale (keyword/tag match over the seed icons)
- Grid-snap: phase the background pattern so a grid line lands on the safe-area
inset, anchoring the composition to the background grid
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Self-serve internal tool to generate on-brand OG (1200x630) and Thumb
images for supabase.com/blog posts. New App Router app at apps/og-generator.
- Render via next/og (satori + resvg), self-hosted Manrope, fontkit text
measurement; all colors/fonts resolve from a design-tokens config (no
hardcoded hex) per the non-destructive recipe model
- Headline: auto sentence-case + acronym allowlist, auto-fit to <=2 lines
(64->40px) with overflow blocking, manual line-break/size override
- 4 guardrailed templates + background pattern library (grid/dots/rules,
opacity locked 4-12%), each template with a default pattern
- Line-art icon system, stroke normalized to 1.22-1.88px; OG + Thumb share it
- Guardrails: char counter (60/70), WCAG contrast check, safe-area overlay,
platform-crop preview
- Stateless /api/og URL + 1x/2x PNG export
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## Context
Noticed that while default opted into unified logs, if you refresh while
on the page, you'll get redirected back to the old logs URL
(logs/explorer)
Happening due to a inconsistent tracking of loading states for feature
flags and feature previews. Just need to track whether the feature
previews have been initialized
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved the loading behavior for unified logs preview so it only
finishes loading after preview settings are fully initialized.
* Added a more reliable initialization state to better reflect when
feature-based defaults are ready.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What
PR 4 of a stacked refactor of the SQL editor snippet/folder state. It
pulls the persistence logic out of the store into an injectable
mechanism, and replaces the folder `'new-folder'` id sentinel with an
explicit lifecycle — plus a concurrency bug fix that surfaced along the
way.
### Save mechanism (`sql-editor-save.ts`)
`createSaveMechanism({ state, upsertContent, createSQLSnippetFolder,
updateSQLSnippetFolder, invalidate, notify, debounceMs })` → `{
saveSnippet, createFolder, updateFolder }`. The store's subscribe now
dispatches to it; *when* to save still lives in the subscribe (the
scheduler/provider move is PR 5). Per-id debounce cache lives in the
factory closure (no module-global leak).
- **`saveSnippet`** reads the live store snippet, guards
`isLoadedSnippet` so a content-less snippet can **never PUT an empty
body** (directly unit-tested), then builds the payload + drives status
transitions + gated invalidation.
- **`toast` is injected** as a `Notifier` (new generic DI contract in
`lib/notifier.ts`) — the mechanism no longer imports sonner.
- **create vs rename are two named-arg functions**, not an `isNew`
branch; rollback is deterministic per operation instead of matching on
`error.message` text.
- **caught errors are `unknown`**, narrowed via the existing
`getErrorMessage` util with a generic fallback — no `any`.
### Folder lifecycle (replaces the `NEW_FOLDER_ID` sentinel)
- **`FolderStatus`** enum (`new_editing | new_saving | editing | saving
| idle`) collapses the persistence and progress axes into one enum —
same pattern as `SnippetStatus` — with `isNewFolder` / `isFolderEditing`
/ `isFolderSaving` predicates. Tagging a folder as new/persisted is now
an explicit field, not an id convention.
- New placeholders get a **unique local id** (`crypto.randomUUID`);
`NEW_FOLDER_ID` is deleted, which also lifts the accidental
one-unsaved-folder-at-a-time limit.
### Bug fix: folder-rename rollback race
The shared `lastUpdatedFolderName` field let two in-flight renames
clobber each other's rollback target (and a shared `finally` could wipe
it). Replaced by a **per-folder `previousName`** on
`StateSnippetFolder`, so concurrent renames of different folders are
isolated. A new test runs two failing renames concurrently and asserts
each restores its own previous name.
## Tests
`sql-editor-save.test.ts` (mechanism — fakes + fake timers, incl.
content-less no-PUT and concurrent-rename isolation) and
folder-lifecycle predicate tests. `pnpm --filter studio typecheck`
clean; 82 state/sql-editor unit tests pass.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Improved SQL editor folder handling with clearer create, rename, and
save states.
* Added a more consistent notification flow for successful and failed
save actions.
* **Bug Fixes**
* Improved rollback handling when folder renames fail, helping restore
the previous name reliably.
* Updated save behavior to better protect against duplicate or
out-of-order updates.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What kind of change does this PR introduce?
Bug fix — closes#45060.
## What is the current behavior?
Saving a large SQL snippet from the SQL Editor fails when the content
exceeds ~1 MB. The content API route (`PUT
/platform/projects/{ref}/content`) relies on Next.js's default API
body-parser limit of `1mb`, so large snippets — for example a
multi-thousand-line RPC — are rejected with a `413 Payload Too Large`
before the handler runs, and the snippet can't be saved.
## What is the new behavior?
The route now sets an explicit body size limit of `5mb`, matching the
limit already used by the AI SQL endpoint
(`pages/api/ai/sql/generate-v4.ts`). Large SQL snippets save
successfully, and the value is consistent with existing SQL-handling
routes in the app.
```ts
export const config = {
api: {
bodyParser: {
sizeLimit: '5mb',
},
},
}
```
## Additional context
- Only the content route's `PUT` handler accepts the snippet body; the
sibling `item/[id].ts` route doesn't take a content body, so no change
is needed there.
- Supersedes the stale #45101 (no activity in ~8 weeks); this version
documents the rationale and aligns the limit with the existing precedent
in the codebase.
---
- [x] I have read the
[CONTRIBUTING](https://github.com/supabase/supabase/blob/master/apps/studio/CONTRIBUTING.md)
guidelines.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed an issue preventing users from uploading or processing large
content, such as SQL snippets, which would previously result in
rejection errors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Context
Filtering on pathname in unified logs shows no data despite the network
request returning some data
Happening due to missing `filterFn` on pathname in `Columns.tsx` (should
just return true so that the react table doesn't bother with client side
filtering, since filtering is done on the server side)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Updated log table filter handling for several always-visible columns,
with no change to the displayed data or user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Context
We're progressively opting in users to use the new Unified Logs UI 🙂🙏
## Changes involved
- [ ] Removed flag for controlling visibility of unified logs feature
preview
- [ ] Added flag for controlling default opt in behaviour of unified
logs
- [ ] Small tweak to Unified Logs banner is default opted in (Just show
"New" and more info CTA)
- Disabling, then enabling again will thereafter show the existing "Go
back to old logs CTA"
<img width="290" height="166" alt="image"
src="https://github.com/user-attachments/assets/a2c46ce1-63c3-490c-bc7d-fc1254982dbe"
/>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Unified Logs preview now derives default opt-in state from a new
default-opt-in flag and exposes `isDefaultOptIn`.
* **Bug Fixes**
* Removed eligibility-based gating so the “Beta” badge and Unified Logs
banner render consistently across logs screens.
* Unified Logs banner was refactored to handle enable/disable and
navigation internally, while remaining shown unconditionally.
* **Tests**
* Updated mocks and assertions to reflect the revised preview/banner
enablement and dismissal logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
## Summary
Adds frontend funnel telemetry to the organization-creation and
project-creation flows in Studio, so each is measurable as a funnel
(form exposed → completed) entirely from frontend events. Feeds the KPI
3 FE Benchmark Friction dashboard. Org creation had zero frontend funnel
events before this (only a backend event that fires across every
surface), and project creation had no clean form-view impression.
## Changes
- Define `organization_creation_form_exposed`,
`organization_creation_completed`, and `project_creation_form_exposed`
in the telemetry constants.
- Fire `organization_creation_form_exposed` when the new-org form
renders, gated on the profile resolving so pre-auth redirects are not
counted. Fire `organization_creation_completed` from the create success
callback, covering both the free and the paid pending-payment-intent
paths, attaching the new org slug as the organization group.
- Fire `project_creation_form_exposed` once the org and the
create-project permission have resolved, so it anchors on the form being
visible rather than the route loading. Project completion reuses the
existing client-side success event, so no duplicate completion event was
added.
## Notes
I chose exposed → completed over exposed → submitted. The org slug only
exists after the create API resolves, so the completion event is the
only org-funnel event that can carry the organization group; a
submit-time event cannot, which would break org-level segmentation. A
pageview is not a sufficient exposure anchor either: pageview capture is
off, and the manual pageview fires on route change before the form is
interactive (pre-auth redirect, async permission load, the no-org
redirect).
The `completed` verb follows the repo's approved-verb list
(`.claude/skills/telemetry-standards`); the repo previously migrated
`branch_merge_succeeded` to `branch_merge_completed` for the same
reason.
## Testing
Tested on the preview deploy:
- [x] `/dashboard/new` while signed in →
`organization_creation_form_exposed` fires once.
- [x] Create a free org → `organization_creation_completed` fires with
the organization group set.
- [x] `/dashboard/new/[slug]` with create permission →
`project_creation_form_exposed` fires once with `surface=main` and the
organization group.
- [x] No event re-fires on re-render or tab refocus.
Post-deploy: confirm in PostHog prod (project 34344) via HogQL that each
event fires with the expected properties and the organization / project
group set.
## Linear
- fixes FE-3690
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added improved tracking for organization and project creation flows,
including when forms are shown and when organization creation completes.
* Captures creation metadata to support better reporting on onboarding
and setup progress.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
The KPI-3 friction dashboard needs to know *why* users hit errors on the
signup, project-creation, and org-creation funnels, not just that they
did. The existing `dashboard_error_created` event already fires for
these paths (10% sampled, with `$pathname`), but carries no reason:
~98.5% of events have no `errorType` and no property carries an error
message. This adds PII-safe classification computed client-side from a
controlled vocabulary, so raw error text never leaves the browser.
Validation errors (previously invisible, since they are inline form
errors that never raise a toast) are now captured on invalid submit.
## Changes
- Extend `dashboard_error_created` with `origin`, `errorCategory`,
`errorReason`, `errorCode`, and a `form` source value
- Add a pure, unit-tested classifier (`funnel-errors.ts`) and a
10%-sampled tracking hook (`use-track-funnel-error.ts`); the classifier
maps errors to stable slugs and emits only slugs + HTTP status, never
raw message text
- Classify signup errors (API failures + validation) in `SignUpForm`
- Classify project-creation errors (API failures, OrioleDB guard,
validation) in the new-project wizard
- Classify org-creation errors (API failures, payment/card declines,
confirm-subscription, validation) in `NewOrgForm`
## Testing
13 unit tests cover every classifier branch (validation / api / network
/ payment, status-code handling, message-pattern matching, and
fallbacks).
To verify on the Vercel preview (events are 10% sampled; set the sample
rate to 1 locally to observe each fire):
- Signup with a weak but non-empty password: `origin=signup,
source=form, errorCategory=validation, errorReason=password_invalid`
- Signup with an already-registered email: `origin=signup, source=toast,
errorCategory=api, errorReason=email_already_registered`
- New project with an empty name: `origin=project_creation, source=form,
errorReason=project_name_invalid`
- New org with an empty name: `origin=org_creation, source=form,
errorReason=org_name_missing`
- New org with a declined test card: `origin=org_creation,
errorCategory=payment`
PII: raw `error.message` is never sent; only controlled slugs and HTTP
status. Dashboard consumers must filter `origin IS NOT NULL` so these do
not collide with the generic toast events the global tracker still
emits.
## Linear
- fixes FE-3691
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added improved, categorized telemetry for signup, project creation,
and organization creation errors, including payment,
subscription-change, and validation failures.
* Extended dashboard error events with optional structured diagnostics
(origin, category, reason, and optional error code) and support for
form-origin reporting.
* **Bug Fixes**
* Improved project-creation handling to record a validation telemetry
event when an Oriole image is unavailable.
* Ensured payment-related and subscription-change failures are captured
consistently alongside existing user toasts.
* **Tests**
* Added unit tests covering API/network/validation/Stripe error
classification and reason mapping.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
The legacy per-service log pages (postgres, auth, api, edge functions,
storage, realtime, cron, etc.) and the single-log detail panel query the
BigQuery-backed `logs.all` analytics endpoint. We are moving these reads
onto the OTEL ClickHouse endpoint (`logs.all.otel`).
## Fix
- Add `Logs.utils.otel.ts`: ClickHouse query builders
(rows/count/chart/single) + row mappers that target the single `logs`
table keyed by `source`, reading fields from the `log_attributes` map
and aliasing columns to the leaf names the renderers expect.
- Parameterize `buildWhereClauses` / `genWhereStatement` in
`Logs.utils.ts` so the OTEL builders reuse the shared nested AND/OR
filter grouping. Defaults keep the BigQuery behavior unchanged.
- Gate `useLogsPreview` (rows, count, chart) and `useSingleLog` (detail)
on the new `otelLegacyLogs` flag. BigQuery stays the default when the
flag is off.
- Extract the OTEL timestamp parser into `parseOtelTimestamp`
(`otel-inspection.utils.ts`) and reuse it in
`unified-logs-infinite-query.ts` (replaces an inline copy of the same
logic; no behavior change).
## Dependencies
None. Standalone, safe to merge on its own. Behind `otelLegacyLogs` (off
by default), so no user-facing change.
Part of DEBUG-145 (split from #47087).
## How to test
- In staging, go to Legacy Logs.
- All logs pages should work the same as before.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added OTEL-backed logs support for preview, count, chart, and
single-log details when enabled.
* **Bug Fixes**
* Improved timestamp parsing/normalization for OTEL data to ensure
correct display and pagination.
* Enhanced filtering behavior, including safer handling of unknown
filter keys and invalid values across OTEL queries.
* Improved single-log result shaping to preserve expected API/database
metadata in OTEL mode.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Reference documentation generation now only includes feature-enabled
SDK pages, so published docs better match what’s available.
* Legacy SDK reference pages are now shown selectively based on enabled
feature flags.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
There's still more unused code in the repository which slows down
everything:
- checkouts
- tooling
- probably builds (not sure how good turbopack is at handling this)
## Solution
- remove old unused code
- remove more recent code after checking git history to ensure it's not
unfinished/ongoing work
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Removed several unused interface, onboarding, and helper components
from the studio app.
* Cleaned up outdated branching, integrations, query performance,
support, and table/grid UI elements.
* Removed a few unused utility hooks and key-mapping logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Context
Found some links pointing to the old logs pages. Should point to unified
logs if unified logs have been enabled
Also deprecates the old `ServiceStatus` file that's no longer used
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Updated observability “logs” links to open the correct view when
unified logs are enabled, including service-specific filtering.
* Fixed navigation from log views to correctly preserve query strings.
* Refreshed project service status log links and health indicators to
stay consistent with the latest unified logs behavior.
* **Refactor**
* Consolidated service status UI and related logic into the project home
experience, replacing the prior shared implementation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES/NO
## What kind of change does this PR introduce?
Bug fix, feature, docs update, ...
## What is the current behavior?
Please link any relevant issues here.
## What is the new behavior?
Feel free to include screenshots if it includes visual changes.
## Additional context
Add any other context or screenshots.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Updated the site’s credits/team listing to include an additional team
member name.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
**Stack 2/6** of the TanStack Start migration (#46424). Stacked on
**#47107** (S1) — review that first; this PR's diff is just the compat
shims.
> [!NOTE]
> Purely additive. Next never imports these files — under TanStack
they're wired in via Vite aliases (`next/*` → `@/compat/next/*`). No
routes consume them yet (that begins in stack 3).
## What's in this PR
`apps/studio/compat/next/*` — drop-in shims so the existing pages-router
code runs unchanged under TanStack Start:
- `link`, `router`, `navigation`, `head`, `image`, `legacy/image`,
`script`, `dynamic`, `server`, `_router-events` — React/runtime shims
over `@tanstack/react-router`.
- `api.ts` — `toWebHandler`, which adapts a pages-router API handler
`(req, res)` into a TanStack server-route Web `fetch` handler.
## Verification
On top of S1: `studio` typecheck ✓, lint (0 errors) ✓. Next build is
unaffected (nothing imports these under tsc).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added broad Next.js compatibility support for routing, links, dynamic
imports, images, scripts, head metadata, navigation hooks, server
responses, and API handlers.
* Improved handling of redirects, pathname/search params, base paths,
and event callbacks for smoother app behavior.
* **Tests**
* Added coverage for URL resolution and dynamic route interpolation to
verify Next-style routing behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
## Problem
The `NoticeBar` component is flagged as deprecated and should be
replaced by `Admonition`
## Solution
- Refactor `NoticeBar` usages to `Admonition` (no visual changes
detected)
- Delete `NoticeBar`
You can see one easily in _Project Settings/Compute and Disks_ and
opening the _Advanced disk settings_ collapsible section
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **UI Improvements**
* Updated disk management alerts and guidance to use a newer, more
consistent notification style.
* Improved visibility of messages for pending disk changes, permission
limits, AWS availability, and advanced disk requirements.
* Refreshed the compute-size upgrade prompt with the same updated alert
styling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What kind of change does this PR introduce?
Bug fix. Resolves DEPR-606.
## What is the current behavior?
On org Security settings, the MFA enforcement switch could appear on
without a green track. Users without personal MFA saw a disabled toggle
with a tooltip.
## What is the new behavior?
- Switch checked state renders correctly (removed tooltip trigger from
the switch).
- Users who need personal MFA first see an admonition with a link to
account security instead of a disabled toggle.
I felt this was a better user experience and more straightforward than
the alternative: fighting the TooltipTrigger’s `data-state` conflict
with the Switch’s checked state.
| Before | After |
| --- | --- |
| <img width="1024" height="563" alt="Security Organization Settings
Toolshed Supabase-4413F7B1-C7DC-4958-8C6F-ADEFDE4F310C"
src="https://github.com/user-attachments/assets/8c71b0d8-db49-4af5-874b-5372df03379d"
/> | <img width="1024" height="563" alt="Security Organization Settings
Toolshed Supabase-ADD5BC82-B433-4EA0-A6BB-874703150663"
src="https://github.com/user-attachments/assets/3c6d3545-fd58-426b-afaf-edd8f7ac4789"
/> |
| <img width="1024" height="563" alt="Security Organization Settings
Toolshed Supabase-F57ED4AA-5A56-4F6A-8F35-569CAC26AFA2"
src="https://github.com/user-attachments/assets/2bc49f34-7819-49fa-ac32-7e59678041fd"
/> | <img width="1024" height="563" alt="Security Organization Settings
Toolshed Supabase-AA795D85-1C17-4C08-9ED1-BBF08C28F2B4"
src="https://github.com/user-attachments/assets/db1822b0-17fc-42df-bdec-0935e46ab5ff"
/> |
| <img width="1024" height="563" alt="Security Organization Settings
Toolshed Supabase-8D8A196F-FA27-4FD3-BC52-DB933E61D59A"
src="https://github.com/user-attachments/assets/bae30a9e-eda9-4a97-845c-0c4751f03a05"
/> | <img width="1024" height="563" alt="Security Organization Settings
Toolshed Supabase-5BD4F063-B402-4E03-ACE4-254BB28C232C"
src="https://github.com/user-attachments/assets/0a3c4d6b-2981-47e9-9679-56bfcd7faf5d"
/> |
## Additional context
Test on `/org/<slug>/security` in light mode with and without personal
MFA enabled. Or just hardcode the ternaries to see the various states on
local.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added clearer guidance for organization security settings when MFA
must first be enabled on a personal account.
* Improved loading behavior while member data is fetched.
* **Bug Fixes**
* Prevented the MFA enforcement form from showing until personal MFA
requirements are met.
* Refined the MFA toggle disabled logic to apply only when appropriate.
* **UI Improvements**
* Replaced the MFA tooltip with an in-page notice.
* Updated the primary action button label from “Save changes” to “Save.”
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
## Context
Realised that we were calling both `useDefaultRegionQuery` and
`useOrganizationAvailableRegionsQuery` in the new project page.
`smartRegionEnabled` defaults to `false` at the beginning while the
flags are still being loaded, to this calls `useDefaultRegionQuery`. But
once the flags are loaded and `smartRegionEnabled` becomes `true`, then
the other hook is called
## Changes involved
- Checks that the flags are loaded first before calling either hooks to
prevent unnecessarily triggering both
- Adjust `defaultRegion` to remove hardcode
- Check smart region first, then default back to default specific region
- Renamed variables to be clearer:
- `autoDefaultRegion` (check based on location)
- `fixedDefaultRegion` (hardcoded in repo)
- Update `useEffect` on `regionError` to only reset the region value if
the default value is not undefined
- Also just a tiny nit to re-arrange to group the `useEffects` together
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Region selection now waits for feature flags to load before fetching
default and “smart” region options, preventing premature or incorrect
region choices.
* New project setup now updates the database region and smart
recommendations more consistently, with improved fallback behavior when
region lookup fails.
* AWS Nimbus default region is now environment-aware: non-prod uses
Southeast Asia, while prod uses East US.
* Default privilege settings now stay in sync more reliably during
setup, updating only when appropriate.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Context
For unified logs, postgres connection logs are being filtered out by
default previously from this
[PR](https://github.com/supabase/supabase/pull/46371) due to its noise.
We're opting to show the connection logs by default instead so this PR
changes that behaviour + adjusts the connection logs filter UI
In particular this is timely as we're adjusting how the DB will log
connections based on this
[changelog](https://github.com/orgs/supabase/discussions/47197), and
we'd want to make sure that users can find their connection logs easily
## Changes involved
- [ ] Search parameter renamed to `show_connection_logs` so that we
don't need to flip its boolean value for the checkbox
- [ ] `show_connection_logs` is subsequently `true` by default
- [ ] Shift connection logs filter to a nested option under Postgres log
type
- Makes it more visual that connection logs are related to the Postgres
service
- Currently its hidden all the way in the bottom under "Misc" which can
be easily missed
- <img width="302" height="151" alt="image"
src="https://github.com/user-attachments/assets/e3e61ac7-aa16-4769-a89e-e911daacea27"
/>
- <img width="289" height="156" alt="image"
src="https://github.com/user-attachments/assets/c70ac7c4-d2f7-4961-a6d6-6653c59d8548"
/>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Unified Logs now supports expandable, nested “Log Type” filter options
for drilling into connection-related entries.
* **UI Improvements**
* Connection logs are visible by default; the visibility control has
been integrated into the main filter experience.
* Filter panels are now streamlined, and key filters (such as “Level”,
“Status”, and “Method”) have been reordered and adjusted for a cleaner
default state.
* **Bug Fixes**
* Updated Unified Logs query/test behavior to match the new
connection-log visibility logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Improved AI, database, storage, and platform guides with clearer
explanations and more detailed workflow steps.
* Added descriptive captions and accessible alt text for multiple
diagrams and benchmark charts.
* Expanded MFA, connection, replication, partitioning, and integration
docs with clearer decision points and setup/login flow guidance.
* Clarified database schema and seed-data examples to better explain how
tables and relationships fit together.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Miranda Limonczenko <miranda.limonczenko@supabase.io>
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Docs to expose current Realtime Broadcast Replay limits.
--
Fixes REAL-874
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Clarified how broadcast replay storage retention works, including the
daily-partition behavior and that replays are dropped after 72 hours
(messages are available for at least 72 hours and up to ~4 days
depending on send time).
* Updated the “Limits by plan” table with broadcast replay retention (72
hours) and broadcast replay messages per request (25) across all plans.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Bump several packages:
- Bump all instances of dompurify (patch version bump)
- Bump `posthog-js` to get a newer version of `@opentelemetry/core`
- Bump `@sentry/nextjs` to get a newer version of `@opentelemetry/core`
- Bump `redocly-cli` to get a newer version of `@opentelemetry/core`
- Bump `undici`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated several project dependencies to newer versions, including
documentation tooling, analytics, and error-tracking packages.
* These updates may improve stability, compatibility, and access to the
latest fixes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Context
Part of consolidating all our code editors - removes all direct renders
of the `Editor` component and use `CodeEditor` instead
## UIs affected
- [ ] Query performance advisor -> query block
- [ ] Table Editor -> Table definition
- [ ] Table Editor -> Text + JSON editor (From RowEditorSidePanel,
expand input field)
- [ ] Auth -> RLS -> Create/edit policy code sections
- [ ] Storage policies -> Anywhere that has a code section
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Rolled out a consistent PostgreSQL code editor experience across
policy, storage policy, trigger function, table definitions, and query
performance screens.
* Updated policy/template previews to use the shared editor for cleaner
read-only viewing.
* **Bug Fixes**
* Removed extra left padding in the query performance editor wrapper.
* Improved the JSON editor action control with clearer icon behavior.
* **Refactor**
* Standardized editor usage by replacing legacy SQL/Monaco-based editors
with the shared CodeEditor and simplifying related editor components.
* Updated CodeEditor capabilities (read-only handling, wrapper styling,
markdown support) and tightened editor prop contracts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Repo cleanup. Removes stray PR-review screenshots that were accidentally
committed to `.github/pr-screenshots/docs-1080/` during the DOCS-1080
work.
## What is the current behavior?
Three PNGs remain in master under `.github/pr-screenshots/docs-1080/`
after PR #47252 merged:
- `pr1-hipaa-compliance.png`
- `pr1-logs.png`
- `pr1-postgres-connection-logging.png`
These were review artifacts and shouldn't live in the repo.
## What is the new behavior?
- Deletes the three PNGs.
- Leaves the `.github/pr-screenshots/` directory otherwise untouched.
## Additional context
Cleanup only — no code or docs change.
### Test plan
- [ ] Confirm the three files are gone from master after merge.
- [ ] Confirm no other file in the repo references those paths.
Co-authored-by: Nik Richers <nik@validmind.ai>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Updated the **API settings** link in the documentation intro to take
users to the correct project settings page.
* Cleaned up the surrounding text formatting so the sentence reads more
naturally.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What kind of change does this PR introduce?
Bug fix.
- Follow-up work to FE-3640
- Contributes to DEPR-604
## What is the current behavior?
Known dynamic OAuth requesters on `/dashboard/authorize` relied on
OAuth-specific hard-coded icon assets that were dark-mode only.
Cursor did not have separate light/dark assets in the shared MCP icon
registry, Perplexity only had a light tile asset with baked-in padding,
and OpenAI used the older blossom mark.
## What is the new behavior?
Known OAuth requester logos now resolve through the shared MCP icon
registry while preserving the existing `SupabaseLogo` treatment for
paired authorisation screens.
Cursor uses transparent SVG light/dark variants, Perplexity has cropped
transparent SVG light/dark variants, and OpenAI/ChatGPT uses the newer
monoblossom SVG in black/white variants. Claude remains static until a
suitable variant is available.
Unknown requester icons still render from the provided URL and fall back
to the requester initial if the image fails.
| Before | After |
| --- | --- |
| <img width="828" height="636" alt="Authorize OpenAI
Supabase-E2A05664-589F-458F-8452-9CEE008D558A"
src="https://github.com/user-attachments/assets/140021b1-ff05-4092-98ef-2eae94ff2ddb"
/> | <img width="828" height="636" alt="Authorize OpenAI
Supabase-EC7E00BD-439A-45D1-8E55-240B227C6897"
src="https://github.com/user-attachments/assets/93e603f2-5cbf-4219-b692-d36ac98e8d2a"
/> |
| <img width="828" height="636" alt="66 Authorize OpenAI
Supabase-CB31FF76-86DB-43A6-A426-46B99B8B1B91"
src="https://github.com/user-attachments/assets/b261416e-39b8-40b3-87fd-461653aa0334"
/> | <img width="828" height="636" alt="Authorize OpenAI
Supabase-EAFCF2F2-5CEA-4FE6-8AC0-819F764B414E"
src="https://github.com/user-attachments/assets/35ad7525-0fa9-4438-b117-4e70b78eb719"
/> |
## To test
1. Navigate to `http://localhost:8082/authorize?auth_id=test-auth-id`
2. Open DevTools → Network
3. Find `/platform/oauth/authorizations/test-auth-id`
4. Right-click → Override content
5. Replace the response body with:
```js
{
"name": "Perplexity",
"website": "https://perplexity.ai",
"icon": null,
"domain": "perplexity.ai",
"scopes": [],
"expires_at": "2026-12-31T23:59:59.000Z",
"approved_at": null,
"registration_type": "dynamic"
}
```
6. Then change "name" to Cursor, Claude, ChatGPT, or OpenAI and refresh
to inspect each logo
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* OAuth app requester logos now dynamically adapt to light and dark
themes, with improved logo selection for known requesters.
* Cursor now uses a distinct dark icon variant.
* Added Perplexity client icon support.
* **Bug Fixes**
* Improved logo rendering robustness: if a logo can’t be loaded, the UI
falls back to the requester’s initial.
* **Tests**
* Expanded coverage for theme-aware logo rendering and icon variant
handling, including unknown-icon and fallback scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Error messages were failing due to a error code collision with Auth.js
in supabase + gotrue, fix that so the error is not suppressed
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **Bug Fixes**
* Removed the "Organization not found" error message that appeared when
navigating to invalid organization slugs.
* Streamlined redirect behavior for empty organization lists.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Supabase Studio > Table Editor > New/Edit Table
## What is the current behavior?
The `Default Value` has a white background and not matching the same
styling:
<img width="776" height="837" alt="Screenshot 2026-06-18 at 13 20 12"
src="https://github.com/user-attachments/assets/b48674d9-20cd-409a-8e9f-387d4fe9f87a"
/>
## What is the new behavior?
Input matches the other styling:
<img width="776" height="837" alt="Screenshot 2026-06-18 at 13 20 25"
src="https://github.com/user-attachments/assets/30cef35a-cab7-4141-bc9c-851e0881d8cb"
/>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Style**
* Updated styling for input fields in the table column editor to provide
a cleaner appearance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Context
For notifications which affect a specific project, there's currently no
indication of when the notification was created at all, so this PR
addresses that
## Changes involved
- For notifications, show created at timestamp in header description
- Was previously showing project ref if present in notification
metadata, but it's repeated information as the affected project is
mentioned in the context section
- It'll still show the project ref in the list view, change is only in
the detail view (after clicking on a notification)
### Before
<img width="400" alt="image"
src="https://github.com/user-attachments/assets/e8ce247c-afa4-46df-832f-856d34ce82fd"
/>
<img width="400" alt="image"
src="https://github.com/user-attachments/assets/2e0a6c8a-3bb4-4c05-ae13-36b8a92e7ff0"
/>
### After
No change for notifications list view
<img width="400" alt="image"
src="https://github.com/user-attachments/assets/e741b607-c5ef-4cd0-9985-5957f2b52bfc"
/>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved how advisor panel details are displayed, ensuring timestamps
and secondary text appear in the right situations.
* Hidden metadata when no relevant information is available, reducing
clutter in the panel.
* **Style**
* Updated a link layout in notification details for cleaner, more
consistent formatting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What
PR 3 of a stacked refactor of the SQL editor snippet state. Replaces the
two overlapping pieces of snippet lifecycle state — the `savingStates`
map (`IDLE|UPDATING|UPDATING_FAILED`) and the `isNotSavedInDatabaseYet`
boolean — with a single `SnippetStatus` enum.
## Status is attached at the data layer (never absent)
- `SnippetStatus` + `SnippetWithContent` now live in `data/content`. The
snippet queries attach `status: 'saved'` via a typed `withSavedStatus()`
helper, and `upsertContent` returns `SnippetWithContent` so move/rename
responses carry status too.
- A SQL-typed `getSqlSnippetById`/`useSqlSnippetByIdQuery` returns
`SnippetWithContent` (the generic `useContentIdQuery` stays for Reports,
which use it). `[id].tsx` loads content with **no casting**.
- `'new'` is attached on local creation (`createSqlSnippetSkeletonV2`).
## Behavior
Behavior-preserving for the existing auto-save flow (faithful mapping of
both old fields, including the replication-lag swallow). One incidental
fix: the read-only/saving indicator now also covers a brand-new
snippet's first save (previously only re-saves of persisted snippets had
distinct saving/failed states in some paths).
## Tests
New `sql-editor-lifecycle.test.ts` (29 tests) covering every predicate
and transition; existing rules tests updated. `pnpm --filter studio
typecheck` clean; 52 state/sql-editor unit tests pass.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **Refactor**
* Restructured SQL snippet persistence tracking, replacing boolean flags
with a comprehensive status system for clearer visibility into save
progress.
* Enhanced saving indicator UI to reflect accurate snippet save states.
* **Tests**
* Added test coverage for snippet persistence state transitions and
lifecycle scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Problem
The `alternative` variant for `<Button>` has been deprecated but is
still used in a few places.
## Solution
- Migrate usages to the recommended `primary` variant
- Delete the `alternative` variant
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Style**
* Updated several buttons across forms, logs, recent queries, and
release headers to use the primary visual style.
* Button styling is now more consistent throughout the app, with a
cleaner default emphasis for key actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->