Commit Graph
100 Commits
Author SHA1 Message Date
Pamela Chia 38b74af3f1 fix(studio): fall back for framework icons without an asset (#51065)
I made the connected-project framework icons fall back when no shipped
SVG exists for a framework. The three icon sites built
`/img/icons/frameworks/<framework>.svg` straight from the integration's
framework preset, which is an open-ended string. They only fell back
when the value was empty, so any preset without an asset (`express`,
`hono`, `fastapi`, `tanstack-start` and others) showed a broken image
and logged a 404.

**Changed:**
- **Broken framework icons**: `getFrameworkIconUrl` returns the asset
URL only for slugs in a set that mirrors `public/img/icons/frameworks/`.
The integration connection row, the org project linker and the
marketplace project picker now show their existing fallback icon for any
other slug. A test keeps the set equal to the directory listing.
- **Framework type**: I deleted the hand-kept `VercelFramework` union.
It listed exactly the shipped icon slugs, while the API types the field
as `string | null`, and that mismatch is what made the old empty-only
check look safe.

**Note:** I rejected an `onError` fallback because the browser still
sends the 404 request. Adding logos for common presets is left for
design.

## To test

Tested on Vercel preview (staging): no real connection there uses these
presets, so I rewrote the org integrations response in the browser to
give one integration four connections.
- [x] Open an org's Integrations page with connections whose framework
has no shipped icon (`express`, `eve`, `tanstack-start-lovable`). Expect
the fallback badge and no request under
`/dashboard/img/icons/frameworks/` for those slugs. Observed: all three
rows showed the badge and the network log had no request for their SVGs.
- [x] Same page with a `nextjs` connection. Expect its framework logo.
Observed: `nextjs.svg` loaded with a 200.
- [x] Same page with the real, unmodified response (one connection with
`framework: null`). Expect the badge, no frameworks requests, and no new
console errors. Observed: as expected.

## Linear
- fixes GROWTH-1309


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Vercel integration and project views now display framework icons when
available and fall back to the Vercel icon when no matching icon exists.
* Framework metadata now supports values beyond a fixed list, while
unsupported frameworks continue to use the fallback icon.

* **Tests**
* Added coverage for supported and unsupported framework icons,
including base-path handling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-30 12:59:36 +08:00
Pamela Chia 9690efeb42 fix(vault): link to current dashboard route (#51058)
I updated first-party Vault links to open the current secrets route
directly. Wrapper credentials, extension metadata, and blog posts still
linked to the retired path and relied on a redirect.

## To test

On the preview:
- [x] Inspect a Wrapper credential's Vault link. Expect
`/integrations/vault/secrets` with a `search` query for that credential.
- [x] Open the inspected target URL. Expect Vault to show the matching
secret.
- [ ] Click a Wrapper credential's Vault link. Expect the filtered Vault
view.
- [ ] Open the pgsodium extension's Vault link and a Vault blog link.
Expect `/integrations/vault/secrets` without the retired route in the
address bar.

## Linear
- fixes GROWTH-1312


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Updates**
* Vault secrets links now direct you to the project’s Integrations page,
including links from wrapper metadata, blog articles, and the `pgsodium`
extension listing.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-30 12:54:28 +08:00
Pamela Chia 99c6e306c7 fix(www): repair legacy reference redirects (#51059)
I repointed legacy reference redirects and first-party links to the
sections the Docs app currently serves. Old client and common-filter
URLs still targeted bare slugs, while SDK landing URLs targeted
`/start`. I preserved v1 auth destinations and linked the retired Dart
v0 migration guide to its original source.

## To test

On the www preview:
- [x] Request `/docs/client/order` with a crawler user agent. Expect a
redirect to `/docs/reference/javascript/using-modifiers-order`.
- [x] Request `/docs/common/filters/_sl` with a crawler user agent.
Expect a redirect to `/docs/reference/javascript/using-filters-rangelt`.
- [x] Request `/docs/reference/kotlin` with a crawler user agent. Expect
a redirect to `/docs/reference/kotlin/introduction`.
- [x] Open the Storage permissions section. Expect its bucket reference
link to target `/docs/reference/javascript/file-buckets-createbucket`.

## Linear
- fixes GROWTH-1293


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Updated links across blog posts, product pages, and feature listings
to point to current JavaScript, Flutter, and Dart documentation.
* Updated legacy documentation redirects to current reference pages,
including filter, modifier, client, and authentication guides.
* Changed reference-root redirects to lead to each language’s
introduction page.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-29 20:48:03 -07:00
Pamela Chia e0e58f8814 fix(studio): redirect moved dashboard routes (#51056)
I added permanent redirects for the moved Dashboard routes that still
send visitors to 404s. Project and organization identifiers carry
through, while the old project billing path opens the organization
picker for billing.

**Note:** The bare `/dashboard/project` path redirects straight to
Organizations instead of the `/dashboard/projects` hop named in
GROWTH-1295, since `/projects` already redirects there.

## To test

Tested on the Studio preview:
- [x] Requested the eight old Dashboard paths in GROWTH-1295 while
signed out. Each returned 308 with the specified destination.
- [ ] Request bare `/dashboard/project` while signed out on the latest
preview. Expect a single 308 to `/dashboard/organizations`.
- [x] Requested a project backup path with a query string. The
destination kept the project ref and query string.
- [x] Requested `/dashboard/project/_`. The project picker remained
reachable.

## Linear
- fixes GROWTH-1295


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Added permanent redirects for legacy Studio routes covering account
and project pages, backups, email templates, edge-function logs,
secrets, and billing settings.
* Redirects preserve incoming query parameters and URL fragments; the
project selector remains unaffected.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-29 23:59:31 +00:00
Pamela Chia 07c75e84fc fix(docs): repair internal content links (#51050)
I fixed stale links in six Docs pages. Guide links now include the Docs
base path, and links to the old Database Hooks route point directly to
the Dashboard Webhooks page.

## To test

- Open the Logs ingest guide in the Docs preview and follow the updated
guide links. Each destination should load.
- Open each affected Docs page in the preview and follow its Webhooks
link. The Dashboard Webhooks page should load after sign-in.

## Linear

- fixes GROWTH-1301


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Updated webhook setup and troubleshooting links to point to the
Integrations Webhooks dashboard.
* Updated Postgres configuration and log-setting links to use current
documentation paths.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-29 16:06:18 -07:00
Pamela Chia 10445cee8e fix(www): redirect bare dashboard paths (#51052)
I added permanent www redirects for inbound Dashboard URLs that
currently reach the www 404 page. The project rule requires a path
segment and carries the full project suffix to Dashboard.

## To test

- In the www preview, open `/login`, `/support/new?category=billing`,
and `/account/tokens`. Each should redirect to its corresponding
`/dashboard` page and preserve the query string.
- Open `/project/<ref>` and
`/project/<ref>/database/migrations?source=docs`. Both should redirect
to the same path under `/dashboard`, preserving the suffix and query
string.
- Open bare `/project`. It should remain outside the new redirect rule.

## Linear

- fixes GROWTH-1302


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Other Changes**
* Visiting `/login`, `/support/new`, or `/account/tokens` now redirects
to the corresponding dashboard page.
  * Project links redirect to the matching dashboard project page.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-29 15:42:45 -07:00
Pamela ChiaandJoshen Lim 4a941518e3 feat(studio): track Explorer runs and saves (#51004)
I added outcome events for Explorer query runs and successful manual
notebook saves. Existing page visits and preview toggles do not show
whether users complete queries or persist notebooks.

**Changed:**
- **Query usage:** Accepted runs from query tabs and notebook cells emit
submitted and terminal outcome events with a shared run ID. Canceled
confirmations emit no run events.
- **Notebook adoption:** Successful manual saves emit created or updated
events. Recreated notebooks count as creations. Unsaved drafts and
failed saves emit neither.
- **Event metadata:** Explorer action events use `Explorer` as their
page title.

**Note:** Assistant-generated saves are outside this PR. Custom
properties omit SQL and notebook content. Page visits still carry the
browser title, which can include a notebook name.

## To test

Tested on the staging preview:
- [x] Run valid and invalid SQL from an Explorer query tab. Each run
emits one submitted event and one matching completed or failed event
with the same run ID.
- [x] Run database and Logs notebook query cells, then add a markdown
cell. The query cells emit matching event pairs; the markdown cell emits
no query event.
- [x] Save a new notebook, then edit and save it again. The successful
saves emit created and updated events.
- [x] Cancel a guarded query. It emits no query run event.
- [ ] Recreate a notebook deleted on the server after local edits. A
successful save emits created, not updated.
- [x] Inspect an Explorer action event request. Its page title is
`Explorer`; page visits still use the browser title.
- [ ] Force a notebook save failure. It should emit no save event. This
case was not tested manually.

## Linear
- fixes GROWTH-1298


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Analytics**
* Explorer query runs are tracked for database and log queries,
including whether they complete or fail.
* Query activity is associated with its location in Explorer, such as a
query tab or notebook cell.
  * Successful notebook saves are tracked as creations or updates.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-09-29 13:54:13 -07:00
Pamela Chia 177ef0cdd6 fix(www): repair broken docs links and redirect /blog/rss.xml (#51002)
## Problem

Four www pages link to docs URLs that return 404:

- `/storage`: the "Learn more" links for analytics and vector buckets
(`storage/analytics-buckets`, `storage/vector-buckets`)
- `/` (frameworks grid): the Vue quickstart (`quickstarts/vuejs`)
- `/edge-functions`: the CI/CD link (`functions/cicd-workflow`; that
page was merged into the deploy guide)
- `/partners`: the Enterprise SSO link (`auth/sso`)

Feed readers also request `/blog/rss.xml`, which renders the blog 404,
while the feed lives at `/rss.xml`. Apart from prefetch requests, it is
the most-requested 404 on www.

## Solution

- Point each link at the live page: `storage/analytics/introduction`,
`storage/vector/introduction`, `quickstarts/vue`,
`functions/deploy#cicd-deployment`, `auth/enterprise-sso`.
- Redirect `/docs/guides/functions/cicd-workflow` to the deploy guide.
This also fixes the older blog post and changelog links to it. The
redirect destination has no anchor because the `.md` redirect generator
in `next.config.mjs` appends `.md` to the destination.
- Redirect `/blog/rss.xml` to `/rss.xml`.

## To test

On the www Vercel preview:
- [x] On `/storage`, click "Learn more" under analytics buckets and
vector buckets: expect the analytics and vector introduction pages. They
navigate to `/docs/guides/storage/analytics/introduction` and
`/docs/guides/storage/vector/introduction`. The www preview doesn't
serve `/docs`, so I confirmed every new docs target returns 200 on
production.
- [x] On `/`, click Vue in the frameworks grid: expect the Vue
quickstart. The Vue tab's "Read docs for Vue" link navigates to
`/docs/guides/getting-started/quickstarts/vue`.
- [x] On `/edge-functions`, click the CI/CD link: expect the deploy
guide scrolled to CI/CD deployment. Opens "Deploy to Production" in a
new tab with `#cicd-deployment` in view.
- [x] On `/partners`, click the Enterprise SSO link: expect the
enterprise SSO guide. Opens "Enterprise Single Sign-On" in a new tab.
- [x] Request `/blog/rss.xml` and
`/docs/guides/functions/cicd-workflow`: expect 308s to `/rss.xml` and
`/docs/guides/functions/deploy`. Both return 308, and `/rss.xml` serves
the feed as XML.
- [x] `/docs/guides/functions/cicd-workflow.md` returns 308 to
`/docs/guides/functions/deploy.md`.
- [x] None of the four pages still links to an old path
(`analytics-buckets`, `vector-buckets`, `quickstarts/vuejs`,
`functions/cicd-workflow`, `auth/sso`).

## Linear
- fixes GROWTH-1297


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Updated quickstart and integration links for Vue, Edge Functions
CI/CD, analytics and vector storage buckets, and Enterprise SSO.
* Added permanent redirects from the old RSS feed and Functions CI/CD
guide URLs to their current locations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-28 21:00:13 -07:00
Pamela Chia 138b654994 fix(studio): point Usage log ingest and query links at logs-* docs (#51001)
## Problem

The org Usage page links added in #50570 point to
`/docs/guides/platform/manage-your-usage/log-ingest` and `/log-query`.
Neither page exists. The docs live at `logs-ingest` and `logs-query`, so
every click from the Usage page lands on a 404. Within a day of #50570
shipping, these two paths were the top docs 404s by unique visitors.

## Solution

- Point both Usage page links at the `logs-*` pages.
- Add permanent redirects from the singular paths, because they're
already being shared: search engines and AI assistants now send people
to them. The existing generator in `apps/www/next.config.mjs` adds the
`.md` variants.

## To test

On the Vercel previews:
- [x] Open an org's Usage page on the Studio preview and click the Log
Ingestion docs link: expect the `logs-ingest` docs page, not a 404.
Opens `supabase.com/docs/guides/platform/manage-your-usage/logs-ingest`
in a new tab ("Manage Logs Ingest usage").
- [x] Click the Log Query docs link: expect the `logs-query` docs page.
Opens `.../logs-query` in a new tab ("Manage Logs Query usage").
- [x] No docs link on the Usage page still points at the singular
`log-ingest` or `log-query` paths.
- [x] On the www preview, request
`/docs/guides/platform/manage-your-usage/log-ingest` and `/log-query`:
expect a 308 to the `logs-*` pages. Both return 308 to the matching
`logs-*` path. The www preview doesn't serve `/docs`, so I confirmed
both targets return 200 on production.
- [x] `log-ingest.md` and `log-query.md` also return 308 to the matching
`logs-*.md` paths.

## Linear
- fixes GROWTH-1296


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Updated the Log Ingestion and Log Query documentation links to their
current pages.
* Added permanent redirects from the previous documentation paths to the
corresponding pages.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-28 20:52:01 -07:00
Pamela Chia 1716d87f59 fix(studio): cap project name at 256 chars (#50804) 2026-09-24 02:34:53 +08:00
Pamela Chia 64ab76262e feat(studio): exhaustion banner links to metrics (#50276) 2026-09-17 22:23:10 +02:00
Pamela Chia 66d4b4c19b chore(studio): remove expired tos update banner (#50533) 2026-09-18 00:52:40 +08:00
Pamela Chia 5e8e551e2c fix(www): include app routes in sitemap (#50277)
I added static App Router pages to the www sitemap, including the
homepage, pricing, and product pages. The generator previously scanned
only Pages Router and content files; it now strips route groups,
excludes dynamic segments, and emits these URLs without lastmod.

**Note:** The pre-existing Pages Router `/opt-out/[ref]` entry remains
outside this change.

## To test

Tested on the [www
preview](https://zone-www-dot-com-git-pamela-growth-1214-app-rou-1d4879-supabase.vercel.app/sitemap_www.xml):

- [x] Open `/sitemap_www.xml`: expect the homepage, `/pricing`, and
product routes once each, without route-group names or lastmod on those
entries.
- [x] Compare the sitemap's changelog URLs with `/changelog-rss.xml`:
expect every RSS item link to remain included, including text-slug
entries.
- [x] Open `/sitemap.xml`: expect the existing www and docs sitemap
links.

## Linear

- fixes GROWTH-1214


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Sitemap generation now includes static pages built with the Next.js
App Router.
  - Route groups are correctly omitted from generated URLs.
  - Dynamic App Router routes are excluded from the sitemap.

- **Bug Fixes**
- Improved sitemap coverage and URL accuracy for applications using both
App Router and Pages Router pages.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-16 16:54:42 +08:00
Pamela Chia f012dfa850 fix(www): sitemap lists all changelog slugs (#50275)
The www sitemap generator reads changelog URLs from the build-generated
RSS feed but only accepted links whose slug starts with a number, the
shape `computeChangelogEntrySlug` produces solely for entries carrying
`legacy_gh_discussion`. Every changelog entry authored since that
migration has a plain text slug and was silently missing from
`sitemap_www.xml`. I widened the link match to any non-empty slug; the
RSS builder is the only producer of that file and emits exactly one link
per item, so no other filter is needed.

I added a text-slug RSS item to the fixture-driven sitemap test and
asserted that the changelog URL list equals the RSS item list, so a
future filter that drops entries fails the suite.

**Note:** text-slug entries now pass through the same fail-the-build
pubDate check that numeric-prefixed entries already did after #50198. A
changelog entry with no `publish_date` and no date-prefixed filename
would produce an unparseable pubDate and stop the www build. The
alternative, shipping the URL without lastmod, is a one-line change. I
kept the gate because every current changelog entry carries a
date-prefixed filename, the changelog repo documents that convention,
and the build error names the entry URL.

## To test

Tested on Vercel preview:
- [x] Count `<item>` blocks in `<preview>/changelog-rss.xml`, then count
`/changelog/` locs in `<preview>/sitemap_www.xml`, expect the two counts
to match
- [x] Search the preview sitemap for `/changelog/pipelines`, expect one
`<loc>` entry with a `<lastmod>` date
- [x] Search the preview sitemap for a numeric-prefixed entry such as
`/changelog/47796-developer-update-july-2026`, expect it still present

## Linear
- fixes GROWTH-1212


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Changelog pages with text-based slugs are now correctly recognized in
the sitemap.
- Sitemap entries for these changelog pages now use their RSS
publication dates.
- RSS links are matched more reliably, ensuring all valid changelog URLs
are included.
- Invalid publication dates are rejected instead of producing incorrect
sitemap metadata.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-12 02:11:25 +08:00
Pamela Chia e315fbcb53 feat(www): emit sitemap lastmod from content dates (#50198)
I added content dates to `sitemap_www.xml` and `dateModified` to blog
JSON-LD so crawlers can compare freshness with page metadata. Both use
`updated` when present, otherwise the publication date.

**Changed:**
- **Consistent dates:** I use the same frontmatter parser for the blog
page and sitemap. It preserves authored dates across quoting and
timezones and rejects JavaScript frontmatter.
- **Invalid dates stop the build:** I reject impossible calendar days,
out-of-range times and offsets, malformed dates, and `updated` before
publication. Content changes run the generator in CI.
- **Authoring:** I documented optional `updated` for substantive
revisions. Events, static pages, and `/evals` omit `<lastmod>`.

**Note:** Changelog dates come from RSS. Existing sitemap omissions for
nonnumeric changelog slugs (GROWTH-1212) and app-router pages
(GROWTH-1214) remain separate.

## To test

On the preview:
- [x] Open `/sitemap_www.xml`: blog, alternatives, customer stories, and
included changelog entries should carry `YYYY-MM-DD` lastmod values.
Verified on the 2092513 preview: all 425 blog, 3 alternatives, 43
customer story, and 207 changelog entries carry a `YYYY-MM-DD` lastmod,
zero malformed values. Production currently emits no lastmod at all.
- [x] Inspect `/blog/supabase-is-now-available-in-gemini-enterprise`:
BlogPosting `dateModified` should be `2026-09-09`, matching its sitemap
entry. Verified: one BlogPosting block, `datePublished` and
`dateModified` both `2026-09-09`, sitemap lastmod `2026-09-09`.
- [x] Find the `/company` and event entries in the sitemap: neither
should carry lastmod. Verified: `/company` and all 13 `/events/` entries
have no lastmod.
- [x] Added: `/evals` and the `/changelog` index carry no lastmod
either.
- [x] Added: the blog page renders with no new console errors. The only
console error is a `/docs?_rsc=` prefetch 404: the preview host serves
404 for `/docs` itself, unrelated to this change.

## Linear
- fixes GROWTH-1206


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Blog posts now support optional updated dates for substantive
revisions.
- Sitemap entries include accurate modification dates for blog,
alternatives, customers, and changelog content.
  - Blog structured data now includes the post’s modification date.

- **Bug Fixes**
- Improved validation prevents invalid or inconsistent content dates
from generating incorrect sitemap data.
- Changelog sitemap links are deduplicated and assigned their published
dates.

- **Documentation**
- Added guidance for specifying publication and update dates in blog
post metadata.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-11 17:20:53 +08:00
Pamela Chia c6a1c2052c feat(www): cross-list openapi and mcp endpoint (#50180)
`/.well-known/ard.json` advertises the Management API OpenAPI spec and
the MCP server, but `/llms.txt` listed neither and
`/.well-known/api-catalog` listed only the Management API. I added both
resources to the two surfaces that were missing them, so an agent finds
the same spec and endpoint whichever discovery file it reads first.

**Changed:**
- **llms.txt gains an `## API and agent resources` section**: two
described links, the same-origin `/openapi.json` spec and
`https://mcp.supabase.com/mcp`, from a small list in
`lib/agent-resources.ts`. A named heading rather than `## Optional`,
since llmstxt.org defines Optional as links an agent may skip. The
descriptions restate ard.json's on purpose; ard.json is curated to the
ARD schema and stays untouched.
- **api-catalog lists the MCP endpoint**: added as a catalog `item` plus
its own linkset member carrying `service-doc` (the MCP guide) and
`service-meta` (the OAuth protected-resource metadata the endpoint's 401
response already points at).
- **Tests cover what the two files advertise**: `ard-catalog.test.ts`
now parses api-catalog, checks that its `item` list and its anchored
members agree, and runs every same-origin URL from api-catalog and the
llms.txt resource list through the existing dead-URL resolver (public
file, app route, rewrite, or docs guide). The www tests workflow now
checks out `apps/docs/content/guides` (the directory the llms.txt route
already reads at runtime) and runs on changes to it, so moving a guide
that a catalog links to fails that PR rather than the next www one.

**Note:** `/openapi.json` is an external rewrite served uncached on
every request (338 KB). I tried `Cache-Control` and then the documented
`x-vercel-enable-rewrite-caching` + `CDN-Cache-Control` pair on that
path; the preview kept returning `x-vercel-cache: MISS`, so both are
reverted. Caching the alias is a separate change.

## To test

Tested on Vercel preview:
- [x] `curl -s <preview>/llms.txt | tail -5`: expect an `## API and
agent resources` heading followed by the OpenAPI spec link and the MCP
server link; the diff against production `llms.txt` is those appended
lines only
- [x] `curl -s <preview>/.well-known/api-catalog | jq '.linkset[2]'`:
expect a member anchored at `https://mcp.supabase.com/mcp` with
`service-doc` and `service-meta`, served as `application/linkset+json`

## Linear
- fixes GROWTH-1207


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added API and agent resource links to `llms.txt`, including the
Management API specification and MCP server.
- Added the Supabase MCP server to the API catalog with service
documentation and metadata links.

- **Tests**
- Expanded catalog validation to cover API catalog entries, agent
resources, and documentation guide links.
  - Updated pull request checks to run when guide content changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-11 16:12:44 +08:00
Pamela Chia 5db8a0e960 feat(studio): instrument sign-in attempts and failures (#49853)
The /sign-in page emitted only a pageview on entry and the success-side
`sign_in` event on exit: failed or abandoned attempts were invisible, so
"never interacted" and "tried and failed silently" could not be told
apart in the sign-in funnel. I added an unsampled `sign_in_submitted`
event at every initiation point and classified failure capture via
`dashboard_error_created` with a new `signin` origin.

**Changed:**
- **Submit attempts observable**: `sign_in_submitted` (method: `email`,
provider id, `sso`, or partner) fires from the DOM submit handler on the
password and SSO forms (so submits that fail client-side validation
still count), and from the OAuth, custom-provider, and partner
initiation handlers.
- **Failures classified**: each sign-in error path feeds the existing
funnel-error pipe with origin `signin` and a controlled reason slug
(`invalid_credentials`, `email_not_confirmed`, `captcha_failed`,
`sso_provider_not_found`, ...). GoTrue auth errors now classify via
their numeric `status`, guarded so transport failures (`status: 0`) stay
`network_error`.
- **Attempt events survive the OAuth redirect**: the telemetry event
POST sends with `keepalive` (scoped to `sign_in_submitted`, since
keepalive requests share a per-page in-flight body quota), so a
dispatched request is no longer aborted by the provider navigation; send
rejections are caught centrally instead of surfacing as unhandled
rejections. The fetch still dispatches after an async token lookup, so
preview testing verifies the GitHub-path event actually lands on the
wire.
- **Captcha rejection is no longer silent**: a rejected hCaptcha
challenge resolves the stuck loading toast with an error message, emits
`captcha_challenge_failed` (distinct from `captcha_failed`, which stays
reserved for the auth server rejecting a submitted token), reports to
error monitoring, and resets the captcha widget (previously: unhandled
promise rejection and a spinner that never resolved).
- **Partner method validated**: the partner sign-in page resolves the
URL-hash value against the provider registry and forwards the canonical
provider id into `method` on both `sign_in_submitted` and `sign_in`;
anything unregistered records as `unregistered_partner`, so a crafted
link can't poison the breakdown on either event.

**Note:** failure events stay on the shared 10%
`dashboard_error_created` sampling rate (a per-origin carve-out would
break cross-source volume comparability); the unsampled attempt event
carries the tried-vs-never-interacted signal at full volume.

## To test

Tested on Vercel preview (studio-staging, wire-level network capture +
staging ingestion check):
- [x] On `/sign-in`, submit a bogus email + password: expect a `POST
*/platform/telemetry/event` request with `action: sign_in_submitted`,
`method: email` in the network tab, plus an error toast. Observed: 201,
auth returned 400 as expected.
- [x] Submit with an empty password: expect `sign_in_submitted` to still
fire (validation failures count as attempts). Observed: event fired with
201 and no auth call followed.
- [x] Click "Continue with GitHub": expect `sign_in_submitted` with
`method: github` on the wire before the provider redirect. Observed: the
POST completed (201) before the browser landed on github.com, so the
keepalive path holds.
- [x] Negative case: fresh page load with no interaction fires no
`sign_in_submitted`.
- [x] Ingestion: all fired events (methods `email`, `github`, plus
organic `sso` submits from a real login on the same preview) arrived in
the staging project with the expected properties.
- [x] Re-ran the email and GitHub paths on the scoped-keepalive build
(`129bf8d`): both `sign_in_submitted` POSTs returned 201 (the GitHub one
completed despite the provider redirect), and both events ingested into
the staging project with the expected `method`/`category` properties.

## Linear
- GROWTH-1165 (no `fixes` keyword on purpose: the evidence checks run on
prod data post-deploy, and the issue closes manually after they pass)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Improved sign-in protection with more reliable invisible CAPTCHA
handling.
* Added sign-in submission tracking across password, SSO, partner,
custom OAuth, and external-provider flows.
* Added detailed classification for authentication, validation, CAPTCHA,
provider, and network errors.

* **Bug Fixes**
  * Sign-in now stops safely and resets CAPTCHA when verification fails.
* Improved error reporting for failed sign-in attempts, including
redirects and OAuth flows.
* Ensured sign-in telemetry is delivered reliably during OAuth
redirects.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-02 19:02:37 +08:00
Pamela Chia 121332c1ac feat(www): add service json-ld to homepage (#49849)
Follow-up to #49768. Agent-readiness scanners grade schema breadth by
extended schema.org types (Service, FAQPage, Product);
SoftwareApplication alone doesn't register, so I added a Service block
whose offer catalog mirrors the products already rendered on the
homepage. I also brought `/.well-known/api-catalog` up to the RFC 9727
API-catalog profile.

**Changed:**
- **Homepage emits Service JSON-LD**: new `serviceSchema` builder in
`lib/json-ld.ts`; the offer catalog lists the six products the homepage
products section renders (Database, Authentication, Storage, Edge
Functions, Realtime, Vector).
- **api-catalog leads with the catalog context**: `linkset[0]` now
anchors the catalog URL and carries an `item` link to the Management API
base, per the RFC 9727 profile; the existing service-desc context moves
to `linkset[1]` unchanged.

## To test
Tested on Vercel preview:
- [ ] View source on the preview homepage: expect a fourth
`application/ld+json` script with `"@type":"Service"` and six offerings
- [ ] `curl <preview>/.well-known/api-catalog`: expect `linkset[0]` to
contain an `item` array pointing at `https://api.supabase.com/v1`

## Linear
- fixes GROWTH-1175


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added structured service information to the home page, including
Supabase’s platform offerings.
  * Added an API catalog entry linking to the Supabase API endpoint.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-01 23:11:18 +08:00
Pamela Chia eba2aeb517 chore: remove stale references to the removed build:llms pipeline (#49848)
## What
The `build:llms` script no longer exists in apps/docs (its output,
`apps/docs/public/llms/*.txt`, is superseded by
`apps/www/app/llms/[slug]/route.ts` serving the generated reference
markdown directly). Four stale references remained:

- `apps/docs/.gitignore`: removed the `public/llms/` entry and its
comment referencing the dead script. Nothing writes to that directory
anymore; if you have leftover local files there, delete them.
- `apps/docs/spec/reference/README.md`: the react-server `tsx` warning
cited `pnpm build:llms` as the consumer. Replaced with `pnpm
embeddings`, a live script that runs under `tsx
--conditions=react-server`. I verified the constraint still holds:
importing `Reference.utils.ts` crashes under `--conditions=react-server`
(in `next/navigation`) and loads fine under plain `tsx`.
- `apps/www/pages/modules/vector.tsx`: the maintenance comment pointed
at `public/llms/vector.txt`, which doesn't exist in www. The
hand-maintained markdown sibling lives at
`content/md/modules/vector.md`.
- `.agents/skills/ask-the-docs/reference/llm-agent-parity.md`: the
"In-flux / stale wiring" bullet asserted the exact `.gitignore` line
this PR deletes (and its "generation path is unclear" caveat no longer
holds; per-source links resolve live via
`apps/www/app/llms/[slug]/route.ts`). Removed the bullet so the
ask-the-docs skill doesn't report a gitignore entry that no longer
exists.

No behavior change; docs and comments only (plus a gitignore entry).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Documentation**
- Updated the embeddings documentation to use the current `pnpm
embeddings` command.
- Clarified where vector module content should be maintained alongside
the corresponding page.
- Removed outdated references to generated per-source LLM files and
retired documentation describing stale generation paths.
- Improved consistency between reference documentation and the current
content-generation workflow.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-01 21:33:54 +08:00
Pamela Chia 3338be76f0 fix(studio): emit sign_in on totp challenge (#49755)
The dashboard's `sign_in` event never fires when a user completes a TOTP
challenge: `SignInForm` only tracks when no MFA challenge is needed, and
the /sign-in-mfa page only tracks on mount when the assurance level is
already satisfied (OAuth/SSO returns). Sign-ins that go through the
actual MFA form were invisible to analytics, and the login audit event
was missing on the same path.

**Changed:**
- **MFA-challenged sign-ins now tracked**: `SignInMfaForm` fires
`sign_in` (reading the same `method` query param the page mount site
reads) plus the login audit event on successful TOTP verification, in
the sign-in context only. The forgot-password flow stays untracked: it
is a reset, not a sign-in.
- **Password+MFA sign-ins report `method: email`**: `SignInForm` now
passes `?method=email` when routing to /sign-in-mfa instead of falling
through to `unknown`.
- **Partner TOTP sign-ins carry their provider**: `SignInPartner` now
passes `?method=<partner>` when routing to /sign-in-mfa, matching the
raw-provider-name convention the other entry points use.
- **Join caveat documented**: the `SignInEvent` doc comment now notes
the event is captured server-side and races the identify call, so it is
not a valid funnel join key across the auth boundary.

## Linear
- fixes GROWTH-1156


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added sign-in method details to MFA redirects for email and partner
authentication, improving sign-in flow tracking.
* Added telemetry and login auditing for successful MFA sign-ins while
keeping forgot-password flows untracked.
* **Documentation**
* Clarified sign-in event tracking coverage, including OAuth providers,
server-side capture, anonymous identifiers, and the sign-in page.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-01 13:26:08 +00:00
Pamela Chia 3dddb60149 feat(www): publish agent discovery catalog and complete json-ld (#49768)
I added the agent-discovery surfaces the www app was missing: a resource
catalog at `/.well-known/ard.json` plus completed structured data on the
homepage. I scoped this from the agent-readiness gaps that are
truthfully closable on the www side; the catalog lists only resources
that already exist and serve 200 (MCP OAuth metadata, Management API
OpenAPI spec, llms.txt, agent-skills index).

**Changed:**
- **Agents can discover our machine-readable resources from one
document**: new static catalog at `/.well-known/ard.json` (Agentic
Resource Discovery format); the legacy `/.well-known/ai-catalog.json`
path serves the same file via rewrite, keeping a single source artifact.
- **Organization JSON-LD carries verifiable company details**: adds
`legalName`, a support `contactPoint`, and the registered address
already public on our Terms of Service.
- **Homepage declares the product as an application entity**: emits
`SoftwareApplication` JSON-LD via the existing
`softwareApplicationSchema` builder, same pattern as the vector module
page.

## To test
Tested on Vercel preview:
- [ ] `curl <preview-url>/.well-known/ard.json`: expect 200 with a JSON
catalog of 5 entries
- [ ] `curl <preview-url>/.well-known/ai-catalog.json`: expect the same
document with status 200 (rewrite, not a redirect)
- [ ] View homepage page source: expect three `application/ld+json`
scripts: Organization now includes `address` and `contactPoint`, and a
`SoftwareApplication` block is present

## Linear
- fixes GROWTH-1164



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added an Agent Resource Description catalog listing Supabase’s MCP,
API, documentation, and agent skill resources.
- Added support for the legacy AI Catalog URL through a canonical
redirect.
- Enhanced website structured data with software application details,
legal information, support contact details, and business address.

- **Tests**
- Added validation ensuring discoverable `.well-known` resources are
cataloged and resolve correctly.

- **Chores**
- Updated marketing site test coverage for `.well-known` resource
changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-01 21:07:46 +08:00
Pamela Chia 8aade77966 fix(www): gate changelog md alternate on slug set (#49754)
Changelog entry pages advertised a `.md` alternate tag unconditionally
while the page is ISR, so an entry published in the changelog repo
between www deploys pointed agents at a `.md` sibling that 404s until
the next build (the static file and `CHANGELOG_PAGES` are both
build-time artifacts). PR #49357 made bare-URL negotiation fail closed
for those entries; I gate the advertising side here the same way.

**Changed:**
- **No more dead `.md` links on freshly published entries**:
`getStaticProps` passes a `hasMarkdownVariant` flag computed from
`CHANGELOG_PAGES` membership and the page renders the alternate tag only
when true. An entry published between deploys carries no tag until the
build that ships its `.md` file; the set reference stays inside
`getStaticProps`, so the generated module stays out of the client
bundle.
- **Drift coverage**: `md-alternates.test.ts` gains the changelog
direction, source-level like the existing `_app.tsx` drift test; the
assertion pins the full `CHANGELOG_PAGES.has(` +
backtick-`changelog/${entry.slug}`-backtick + `)` expression so a
dropped key prefix fails the suite, and removing the gate fails it too.

**Note:** without changelog sync secrets `CHANGELOG_PAGES` is empty, so
the tag never renders in local dev. Preview and prod are the
verification surface.

## To test
Tested on Vercel preview:
- [x] Open a published changelog entry page and view source: expect
`<link rel="alternate" type="text/markdown"
href="/changelog/<slug>.md">` in the head — observed exact href
`/changelog/19669-supavisor-1-0.md`
- [x] Fetch that href: expect 200 with `content-type: text/markdown` —
observed 200, `text/markdown; charset=utf-8`
- [x] (added) Client-side nav from `/changelog` into an entry: alternate
tag appears with that entry's slug; hopping to a second entry updates
the href (no stale tag)
- [x] (added) Navigating back to `/changelog`: entry tag gone; the index
shows its own pre-existing `/changelog.md` alternate (hardcoded in
`pages/changelog.tsx`, outside this diff), and `/changelog.md` returns
200 `text/markdown`
- [x] (added) Console: zero new errors across all scenarios vs page-load
baseline

## Linear
- fixes GROWTH-1120


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Changelog pages now advertise a Markdown alternate link only when a
Markdown version is available.
* Prevented links to unavailable Markdown content from appearing on
changelog entries.

* **Tests**
* Added coverage to verify correct Markdown alternate detection and
rendering.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-31 16:25:06 +08:00
Pamela Chia 9d5c19e580 chore(repo): add public-surfaces rule to agent instructions (#49750)
## 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 maintenance: one bullet added to the committed agent instructions
in `.claude/CLAUDE.md`.

## What is the new behavior?

Agent sessions working in this repo (including Slack-triggered ones) get
an explicit rule that PR descriptions, issues, and code comments are
world-readable, so internal content stays out of them: absolute
production metrics (percentages, ratios, or relative change instead),
internal decision detail (vendor, legal, pricing, or strategy
discussions), and competitor names (protocol identifiers such as
user-agent strings are fine). That context goes in the linked Linear
issue. I added this after an agent-authored PR quoted absolute internal
event volumes in its description.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Added guidance to keep public PRs, issues, and code comments free of
sensitive internal details.
* Clarified that production metrics, internal decisions, and competitor
information should be documented privately instead.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-31 15:53:20 +08:00
Pamela ChiaandAleksi Immonen 35531ea2f4 feat(www): serve openapi spec at /openapi.json (#49587)
Agent-readiness scanners and agent fetchers look for an OpenAPI spec at
conventional same-origin paths, but the Management API spec is only
served on api.supabase.com and linked from the /.well-known/api-catalog
linkset, which scanners do not read. I added a rewrite so
supabase.com/openapi.json proxies the spec from its source of truth at
api.supabase.com/api/v1-json, using the same fall-through proxy
mechanism as /humans.txt and /evals.

**Note:** no cache or CORS headers on purpose: no consumer needs them
today, and the upstream response's set-cookie header defeats edge
caching regardless. I rejected a checked-in copy of the spec in favor of
proxying live (staleness). The /.well-known/api-catalog linkset already
points at the spec (PR #44880) and is untouched here; this PR only adds
the conventional same-origin path.

**Merge order:** merge only after supabase/platform#37571 deploys. The
spec currently ships `servers: []`, so OpenAPI consumers resolve
relative paths against the fetch origin; without the platform fix this
proxy would point spec-compliant clients at supabase.com/v1/*.

## To test
Tested on Vercel preview:
- [x] `curl -s https://<preview-url>/openapi.json | head -c 40` returns
`{"openapi":"3.0.0"`
- [x] `curl -sI https://<preview-url>/openapi.json` returns 200 with
`content-type: application/json`
- [x] `curl -sI https://<preview-url>/humans.txt` returns 200 (control:
rewrite fall-through chain intact)

## Linear
- fixes GROWTH-1138


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added access to the OpenAPI specification at `/openapi.json`.
  * Requests are automatically routed to the API specification endpoint.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Aleksi Immonen <aleksi@supabase.io>
2026-08-28 17:26:43 +08:00
Pamela Chia 164de2c347 feat(www): markdown 404 for markdown-negotiated paths (#49596)
Nonexistent paths return a real 404 everywhere, but always with an HTML
body, even when the client asked for markdown via `Accept:
text/markdown` or a `.md` suffix. Middleware can't fix this: it gates on
a static slug allowlist and can't know a path will 404. I added two
`fallback` rewrites (`.md` suffix; Accept header containing
`text/markdown` or `text/*`) that run only after every route has failed
to match and route the request to a small `md-404` handler returning a
short markdown 404 pointing at /docs, /sitemap.xml, and /llms.txt. Real
pages are structurally unaffected.

**Note:** `lib/rewrites.js` is untouched (the plain rewrites array
became the `afterFiles` phase), so #49587 merges independently. I
updated next.config.test.ts's rewrites assertion for the phased shape;
it now also pins the two fallback rules.

## To test

I verified on the Vercel preview:
- [x] `curl -s -D - -H "Accept: text/markdown"
<preview>/definitely-not-a-page` (404, `Content-Type: text/markdown`,
body with the three pointers)
- [x] Same URL with a browser Accept header (existing HTML 404,
unchanged)
- [x] `curl -s -D - <preview>/definitely-not-a-page.md` (markdown 404)
- [x] `curl -s -D - -H "Accept: text/markdown" <preview>/auth` (200
markdown, unchanged) and `<preview>/support` (200 HTML, unchanged)
- [x] `/homepage.md` still 308s to `/index.md` (redirects phase wins);
`Accept: text/*` gets the markdown 404, matching real-page negotiation

Known boundary: `/changelog/<unknown>` keeps the HTML 404 body
(pages-router `fallback: 'blocking'` routes take priority over fallback
rewrites per Next docs); the status is still 404, verified on the
preview.

## Linear
- fixes GROWTH-1142


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added Markdown-formatted 404 responses for unmatched documentation and
`.md` page requests.
  - Included helpful documentation links in not-found responses.
- Requests that explicitly accept Markdown now receive a consistent
Markdown response.
- Added appropriate response headers for security, caching, and content
variation.

- **Bug Fixes**
- Improved routing for unmatched Markdown paths, ensuring they are
handled by the appropriate not-found response instead of returning an
unexpected format.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-27 13:29:04 +10:00
Pamela Chia 013af4ed58 feat(www): homepage json-ld, canonical, 404 links (#49533)
An agent-readiness scan of supabase.com (is-agentic.com, public report)
flagged that the homepage serves no structured data and no canonical tag
in raw HTML, and that the 404 page gives crawlers and agents no recovery
path. I fixed both.

**Changed:**
- **Homepage structured data**: the raw HTML now carries Organization
and WebSite JSON-LD plus `<link rel="canonical"
href="https://supabase.com">`. The schema builders already existed in
`lib/json-ld.ts` but were never wired to any page; this reuses the exact
inline-script pattern from the blog post pages. The canonical is
hardcoded to the production origin on purpose: `SITE_ORIGIN` resolves to
the branch URL on previews.
- **404 recovery links**: the 404 body now links to the docs, the
sitemap, and llms.txt, so a dead URL leads somewhere instead of a dead
end. The decorative giant "404" backdrop is a `div` instead of a second
`h1`, marked `aria-hidden`, and gets `pointer-events-none`: browser
testing showed the absolutely positioned backdrop was silently
swallowing clicks on the new links (positioned elements paint above
static siblings for hit-testing even when visually behind).
- **Drift-guard test**: `md-alternates.test.ts` asserted the literal
one-liner `alternates: mdAlternates('<slug>')`, which the canonical
wrapper breaks. I broadened the assertion to accept the spread shape
too; the rule it guards (every markdown-served slug advertises its `.md`
sibling) is unchanged and still enforced.

## To test
Tested locally against the dev server:
- [x] `curl -s localhost:3000` and parse the two `application/ld+json`
blocks: both valid JSON, types Organization and WebSite
- [x] `curl -s localhost:3000 | grep canonical`: expect `<link
rel="canonical" href="https://supabase.com"/>`, with the existing
`text/markdown` alternate link still present
- [x] `curl -s localhost:3000/some-nonexistent-page`: expect HTTP 404
with hrefs to `/docs`, `/sitemap.xml`, `/llms.txt` and exactly one
`<h1>` in the body

On the Vercel preview (verified via curl + Playwright browser run):
- [x] View source on the preview homepage: the two JSON-LD blocks
present and a canonical pointing at `https://supabase.com` (prod origin,
even on the preview host)
- [x] Open a nonexistent preview URL: 404 page renders the new link row
under the "Head back" button, visually unchanged otherwise (screenshots
in session records)
- [x] Added: click each recovery link: element hit-testing returns the
anchor for all three, and clicking Sitemap navigates to a valid
`/sitemap.xml` document (this check caught the pointer-events
regression, fixed in this PR)

## Linear
- Part of GROWTH-1124 (kept open: remaining scan findings are tracked in
a sub-issue)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **SEO & Discoverability**
- Added canonical URL metadata and structured organization and website
information to the homepage.
- Improved 404 page navigation with links to Documentation, Sitemap, and
`llms.txt`.

- **Accessibility**
- Updated the 404 page’s decorative background marker to be
non-interactive and hidden from screen readers.
  - Added reduced-motion handling for page transitions.

- **Tests**
- Updated metadata validation to support multiple alternate metadata
configurations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-26 16:41:46 +08:00
Pamela Chia 6d4263bf1a fix(www): pricing table spacing and sticky offset (#49532)
Multi-line cells in the /pricing comparison table rendered as one
cramped block once lines wrapped: the stacked values had no gap between
entries, and the continuation lines' `leading-4` is a no-op at
`text-xs`. At the same narrow desktop widths (1024 to 1280px), category
headers clipped behind the sticky plan header because their hardcoded
`top-[108px] xl:top-[84px]` offsets desync from the plan header's
variable height.

**Changed:**
- **Value cells get vertical padding**: the value tds were `pl-6 pr-2`
with no vertical padding, so tall multi-line cells pressed text against
the row dividers; they now carry `py-5` like the row-label column (text
ink sits ~21px off both borders, measured).
- **Stacked price lines read as separate entries**: `gap-2` between
entries while wrapped lines inside one entry stay tight (`leading-4`),
so each price reads as its own block. Two earlier iterations that only
widened the entry gap (`gap-1`, then `gap-2` with looser `leading-5`
wraps) reviewed as still-cramped; the missing cell padding was the
dominant cause. Mobile untouched.
- **Category headers never clip behind the plan header**: a
ResizeObserver measures the sticky thead's real height and publishes
`--pricing-category-top` as a CSS variable on the table; the category
header consumes it via `var()`, keeping the old 108px/84px values as
pre-hydration fallbacks so SSR paint is unchanged. Runs in a layout
effect so the first client paint already has the measured value.

**Note:** I rejected re-tuning the hardcoded offsets: numbers desyncing
from the header's content-driven height is the root cause, and new
constants re-break on the next copy or breakpoint change.

## Before / after

**Before** (prod: multi-line prices pressed against the row dividers as
one dense block; rows clipped under the sticky category header):
<img width="1442" height="450" alt="pr-before"
src="https://github.com/user-attachments/assets/91d428df-e04c-4494-b454-84a8a46b3ddd"
/>

**After** (this PR: padded cells, each price its own block, headers pin
flush):
<img width="1497" height="375" alt="pr-after"
src="https://github.com/user-attachments/assets/5aa2c3c7-b41e-42c0-8159-bb294a5d1dea"
/>

## To test

Tested on the Vercel preview (Playwright, measured values in parens):
- [x] At a 1024 to 1280px viewport, open /pricing and find the Pipelines
row: the 3 price lines in the Pro and Team cells read as distinct blocks
(row-gap 8px between entries, tight 16px line boxes within an entry,
20px cell padding; verified on localhost pre-push and on the preview,
light and dark)
- [x] Multi-line cell text no longer touches the row dividers: ~21px
from border to text ink top and bottom (was 0-3px)
- [x] At the same width, scroll the whole comparison table: Database and
Auth section headers pin flush below the plan-price header, with no row
text clipped between them (pinned category top within 0.2px of thead
bottom)
- [x] At ~1800px wide: Pipelines and Point in time recovery (Enterprise)
cells show the same separated lines (PITR Enterprise is a single
wrapping string, renders cleanly)
- [x] Resize across 1280px after load: category headers stay flush under
the plan header (ResizeObserver refires; same 0.2px alignment after
1900px to 1150px resize without reload)
- [x] Below 1024px: the mobile comparison view is unchanged
- [x] Added: cold navigation to /pricing#compare-plans lands with
`--pricing-category-top` already applied (151px at 1150px width) and the
pinned header flush
- [x] Added: no new console errors versus the page's pre-existing
baseline

## Linear
- fixes GROWTH-1137
2026-08-26 15:11:15 +08:00
Pamela Chia 21a27eeb4f feat(www): canonicalize homepage markdown at /index.md (#49384)
The www root markdown lived at an accidental URL: `/.md` served the
homepage markdown only because middleware strips the `.md` suffix and
the empty slug fell through to the homepage allowlist entry, while the
canonical-looking `/index.md` 404'd. The served markdown also opened
with stale legacy positioning copy that no longer matches the site. I
renamed the homepage content slug to `index` end-to-end so `/index.md`
is the one canonical markdown URL.

**Changed:**
- **`/index.md` serves the homepage markdown (200 `text/markdown`)**:
`content/md/homepage.md` renamed to `index.md`; the middleware bare-root
slug mapping, the generator's sort special-case, and the homepage
alternate tag follow, so the tag now advertises `/index.md`.
- **Legacy aliases 308 to the canonical URL**: `/.md`, `/homepage.md`,
and bare `/index` redirect via `lib/redirects.js`; `/llms/homepage.txt`
retargeted straight to `/index.md` to avoid a redirect chain. New
`next.config.test.ts` assertions pin all four.
- **Positioning refreshed**: the markdown now opens with "Supabase is
the Postgres development platform" (matching the site title), replacing
the outdated tagline.
- **Generator safety**: the redirect-exclusion filter in
`generateMdContent.mjs` now exempts the `index` slug (its HTML page is
`/`, not `/index`, so a `/index` redirect never refers to it), and the
build fails if `content/md/index.md` ever goes missing while middleware
still maps `/` to the `index` slug.
- **CI actually runs the new assertions**: I widened the `www-tests.yml`
paths filter to include `apps/www/lib/**/*.js`,
`apps/www/content/md/**`, and `apps/www/scripts/**/*.mjs`. It previously
only matched `.ts*` and the next.config files, so a PR touching only
`lib/redirects.js`, the markdown content, or the generator would skip
the tests that pin these redirects.

**Note:** the existing homepage alternate tag still exists, re-pointed
to the canonical URL. Whether the homepage should advertise a markdown
sibling at all is a separate decision; leaving it aimed at a 308 would
break tag consumers. Positioning wording is editorial, happy to tweak.

## To test
Tested on Vercel preview:
- [x] `curl -si <preview>/index.md`: expect 200 `content-type:
text/markdown`, body opens with the Postgres development platform
positioning and no longer contains the old tagline
- [x] `curl -sI <preview>/.md`: expect 308 with `location: /index.md`
- [x] `curl -sI <preview>/homepage.md` and `curl -sI
<preview>/llms/homepage.txt`: expect 308 with `location: /index.md`
- [x] `curl -sI <preview>/index`: expect 308 with `location: /`
- [x] `curl -s -H "Accept: text/markdown" -o /dev/null -w "%{http_code}
%{content_type}" <preview>/`: expect `200 text/markdown` (bare-URL
negotiation unchanged)
- [x] `curl -s <preview>/ | grep -o 'type="text/markdown"
href="[^"]*"'`: expect href ending `/index.md`

## Linear
- fixes GROWTH-1117



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added support for `/index.md` as the canonical Markdown representation
of the homepage.
- Added permanent redirects for legacy homepage Markdown and text URLs.
  - Added `/index` to `/` redirect handling.

- **Bug Fixes**
- Updated homepage metadata, alternate links, Markdown negotiation, and
content generation to consistently use the new canonical path.
  - Improved homepage content description.

- **Tests**
- Expanded coverage for homepage Markdown routes, redirects, and URL
matching.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-24 15:19:56 +08:00
Pamela Chia f208743432 fix(www): changelog md negotiation slug-set gate (#49357)
Bare-URL `Accept: text/markdown` negotiation never fires on changelog
entries authored after the GitHub-discussions backfill: the middleware
gate `/^changelog\/\d+/` only matches legacy numeric slugs (from
`legacy_gh_discussion` frontmatter), so agents that signal markdown via
Accept get HTML on every new entry. I found this in the independent
review round on #48475; pre-existing, not introduced there.

**Changed:**
- **Non-legacy entries negotiate markdown**: `generateMdContent.mjs` now
lists `public/changelog/*.md` (written moments earlier by
`generateStaticContent.mjs` in the same `content:build:core` chain) and
emits a `CHANGELOG_PAGES` set into the generated module; the middleware
regex becomes a set lookup, so negotiation coverage derives from the
exact static files served and can't drift from what's published.
- **Unknown and deep changelog paths stop negotiating**: the old regex
prefix-matched paths like `changelog/100/bar` and nonexistent numeric
slugs, rewriting them to missing `.md` files (404 under a markdown
Accept); they now pass through to the dynamic route's canonicalizing
308/404.
- **Build guard**: zero collected changelog slugs on Vercel fails the
build (today a zero-entry changelog fetch ships empty output with a
green build), and a shape assertion fails the build if collected slugs
ever lose the `changelog/` prefix the middleware matches on. Locally
without `CHANGELOG_SYNC_APP_*` secrets it warns and changelog
negotiation is off, matching the absent content.
- **`/changelog` index gated the same way**: the index slug is emitted
into the set only when `public/changelog.md` was generated, replacing
the hardcoded `slug === 'changelog'` branch; locally without secrets the
index no longer rewrites to a nonexistent file.

**Note:** script order in `content:build:core` is load-bearing (static
content generation must precede md content generation); the Vercel guard
turns a reorder into a loud build failure instead of a silent empty
gate.

## To test
Tested on the Vercel preview (`zone-www-dot-com` deployment of head
`f451da3`):
- [x] `curl -sI -H "Accept: text/markdown" <preview>/changelog` and
`curl -sI <preview>/changelog.md`: got 200 `text/markdown` (index via
the generated gate)
- [x] `curl -sI -H "Accept: text/markdown"
<preview>/changelog/pipelines`: got 200 `text/markdown` (prod today
returns `text/html`)
- [x] Same curl against the legacy numeric slug
`48235-migration-of-...`: got 200 `text/markdown` (no regression)
- [x] `curl -sI -H "Accept: application/json"
<preview>/changelog/pipelines`: got 406 (prod today returns 200 HTML)
- [x] Explicit `.md` fetches for both slug shapes
(`/changelog/pipelines.md`, `/changelog/48235-....md`): got 200
`text/markdown`
- [x] `curl -sI -H "Accept: text/markdown"
<preview>/changelog/does-not-exist-xyz`: got a 404 HTML passthrough from
the dynamic route, not a 406
- [x] `pnpm test middleware.test.ts` in `apps/www` at head: 41/41 pass
(36 pre-existing + 5 new). No CI job runs the www vitest suite, so this
local run is the only oracle for the new tests.

## Linear
- fixes GROWTH-1062


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Improved changelog page handling, including markdown versions of
published entries.
- Added content negotiation for supported changelog formats, with clear
responses for unsupported requests.
- **Bug Fixes**
- Prevented unpublished numeric-prefix pages from being treated as
published.
  - Fixed deep links under published changelog entries.
- **Reliability**
- Changelog availability is now detected automatically, with improved
validation during content generation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-21 15:01:50 +08:00
Pamela Chia 10c425ad0b feat(www): markdown copy/ask affordances (#48475) 2026-08-21 08:51:11 +08:00
Pamela Chia 65e786ba13 feat(docs): manifest-gated markdown alternate helper (#48389) 2026-08-20 19:18:47 +08:00
Pamela Chia 9ae6e54dd5 fix(www): exclude redirected slugs from generated markdown (#48476)
11 of the generated `MD_PAGES` entries are blog slugs whose HTML pages
308-redirect away via `apps/www/lib/redirects.js` before any `<head>`
renders. They can never carry an alternate tag and Accept negotiation
never fires (Next.js `redirects()` runs before middleware), so their
`.md` siblings are orphaned content reachable only by guessing the
suffixed URL. Six of them duplicate live, correctly-tagged pages
(`/customers/*`, `/pricing`).

**Changed:**
- `generateMdContent.mjs` derives an exclusion set from
`lib/redirects.js` at generation time: any unconditional exact-match
redirect source (wildcard/param patterns and conditional `has`/`missing`
redirects are skipped) drops the matching slug from both `MD_CONTENT`
and `MD_PAGES`. The build log names every excluded slug, currently the
11 known ones.
- Self-maintaining by design (per the decision recorded on the issue): a
future redirected post auto-excludes on the next build, and removing a
redirect brings its `.md` sibling back. The MDX sources stay in the
repo; nothing is deleted.
- Effect on the 11 slugs: alternate tags stay absent (nothing rendered
them anyway), and explicit `.md` URLs go from serving orphaned markdown
to 404, the same external effect deletion would have had.

## To test

Tested locally:
- [x] `node scripts/generateMdContent.mjs` logs `🚫 Excluded 11
redirected slugs: ...` naming exactly the 11 known slugs; output drops
483 → 472 pages
- [x] Generated file carries no MD_CONTENT/MD_PAGES key for any excluded
slug (raw URL mentions inside other posts' bodies remain, as expected)
- [x] `apps/www` vitest: 71/71 (GROWTH-1013 drift tests unaffected)

Post-merge:
- [ ] `https://supabase.com/blog/case-study-xendit.md` returns 404
(previously 200 orphaned markdown); `https://supabase.com/pricing.md`
still 200

## Linear
- fixes GROWTH-1022


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Excluded content with valid exact-path redirects from generated
documentation.
  * Preserved content associated with conditional or wildcard redirects.
* Updated generated page counts and output statistics to reflect the
filtered content.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-20 15:23:13 +08:00
Pamela Chia c4c58ef3e3 feat: remove pandadoc dpa request flow (#48525)
Terms of Service v3 (effective August 1, 2026, #48482) incorporates the
Data Processing Addendum by reference, so customers no longer sign a
separate DPA. Legal confirmed the PandaDoc signing flow can go;
previously signed DPAs remain binding. This removes the frontend flow
only. I'll remove the platform endpoint (`POST
/platform/organizations/{slug}/documents/dpa`) separately once the
PandaDoc contract conversation wraps.

**Changed:**

- **Dashboard DPA card no longer requests PandaDoc documents**: the
Request DPA button and confirm modal are replaced with a View DPA link
to the canonical legal page, with evergreen copy explaining the DPA is
part of the Terms. Tracked via the same `document_view_button_clicked`
event the other document cards use.
- **Legacy `/legal/dpa` page retired**: the page told users to request a
signed DPA from the dashboard, which no longer exists. It now
permanently redirects to
`/legal/customer-resources/data-processing-addendum` (the follow-up
already flagged in #48483), and the footer link is removed. The
`dpa_pdf_opened` and `dpa_request_button_clicked` events are removed
with their last call sites. The latest privacy version links the
canonical page directly; archived v1/v2 keep their original `/legal/dpa`
link, served by the redirect.
- **Orphaned DPA PDFs removed**: the four dated `Supabase+DPA+*.pdf`
files under `/downloads/docs` had zero remaining references once the
signing flow is gone. No redirect: nothing links these URLs, so they
404.
- **Subscription tracking**: the subprocessor updates form now fires
`www_subprocessor_updates_subscribed` on successful submit, so we can
measure uptake of the notification list that replaces per-customer DPA
emails.

## To test

Verified on the Vercel previews (Playwright):

- [x] Studio: `/org/_/documents` shows the DPA card with the
incorporation copy and a working View DPA link (href = canonical page);
no Request DPA button, no PandaDoc mention; TIA/SOC2/ISO27001/HIPAA
cards unaffected
- [x] www: `/legal/dpa` permanently redirects to
`/legal/customer-resources/data-processing-addendum`; footer no longer
shows DPA; zero console errors
- [x] www: subscribing on the subprocessor page succeeds (200 from the
form route, profile created with topic_4) and fires
`www_subprocessor_updates_subscribed` (201 from the telemetry endpoint);
test profile unsubscribed afterwards
- [x] www: `/downloads/docs/Supabase+DPA+260601.pdf` returns 404 with no
redirect; DPA card copy verified without the effective date

## Linear

- fixes GROWTH-1068
2026-07-31 16:18:25 +08:00
Pamela Chia 4ae0c08967 feat: tos v3 update banner + publish subprocessor list (#48524)
Terms of Service v3 (effective August 1, 2026, #48482) incorporates the
Data Processing Addendum by reference, and Legal asked for an in-app
notice announcing the change. The subprocessor list page that the new
Terms, DPA, and notice all point at was merged as an intentionally
hidden draft (#48100) and never un-hidden.

**Changed:**

- **Dashboard ToS-update banner**: re-enables `BannerTOSUpdate` with the
v3 copy provided by Legal (DPA incorporation, subprocessor list
location, fees provisions). New expiry (August 29) and a new
localStorage key, since anyone who dismissed the May v2 banner would
otherwise never see this one.
- **Subprocessor list page published**: removes `noindex,nofollow` and
links the page from the Legal Hub index, so the page customers are told
to subscribe on is actually discoverable.
- **Studio e2e fixture updated**: the global Playwright fixture
suppressed the banner via the old localStorage key; with the gate live
again it would have rendered the banner into every e2e run. It now sets
the new key.

## To test

Verified on the Vercel previews :

- [x] Studio: banner renders on dashboard load with the Notice badge and
new copy; Learn more dialog shows the three changes with correct hrefs
(DPA page, subprocessor list, /terms); Understood dismisses and persists
across reload via `terms-of-service-update-2026-08-01`
- [x] www: `/legal` lists Subprocessor List under Customer Legal
Resources; `/legal/customer-resources/subprocessor-list` serves `robots`
meta `index,follow` and renders the download button + subscribe form;
zero console errors on all tested pages

## Linear

- fixes GROWTH-1067


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a publicly accessible Subprocessor List to the legal resources.
* Updated the Terms of Service notice to reflect the August 1, 2026
update, including data processing, subprocessors, fraud prevention, and
consumer provisions.

* **Documentation**
* Made the Subprocessor List discoverable through standard search
indexing and the legal resources page.
* Extended the Terms of Service banner availability through August 29,
2026.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-31 15:49:08 +08:00
Pamela Chia 01541b95cb fix(studio): keep organization_slug in oauth signup redirect (#48352)
Email signups inside a partner OAuth flow lose `organization_slug` on
the post-confirmation redirect: the OAuth branch in `SignUpForm`
hand-builds the `/authorize` return URL from only `auth_id` and `token`,
and the component's nuqs hook never reads the param at all. The consent
screen uses `organization_slug` to preselect and lock the partner's
requested org, so affected multi-org users land on an empty picker and
the partner's requested org is silently dropped. The GitHub-OAuth signup
path goes through `buildPathWithParams` and preserves the param, which
is how this went unnoticed.

I validated the drop in production traffic before fixing: joining
sign-up pageviews to their post-signup `/authorize` return on the
`auth_id` URL param (30d), 28 of 36 resolvable flows came back without
the slug, and the 8 that kept it were the GitHub branch.

## To test

Needs a partner OAuth authorize link that includes an org, opened
signed-out: `/dashboard/authorize?auth_id=<id>&organization_slug=<slug>`
(note `auth_id` records expire quickly, so generate a fresh authorize
request from an OAuth app).

- [x] Sign up with email from that flow; after confirmation the redirect
lands on `/authorize` with `organization_slug` still in the URL
- [ ] Consent screen shows the requested org preselected and locked
- [x] Same flow without `organization_slug` behaves as before (no
trailing empty params in the redirect URL)

## Linear

- fixes GROWTH-1031




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved sign-up redirects during authentication flows by preserving
invitation tokens and organization information.
  * Enhanced handling of sign-up links containing organization details.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 23:02:05 +08:00
Pamela Chia 9113c2ba04 feat: markdown alternate tags + llms.txt cleanup (#48287)
The June/July marketing redesign (#47271, #47228) rebuilt the homepage
and product pages off the Pages Router, silently dropping their `<link
rel="alternate" type="text/markdown">` head tags, and llms-full.txt has
been accidentally embedding every blog/customer/event page via an
`MD_CONTENT` spread. I restored the tags behind a shared helper, added a
CI drift test so a future redesign can't drop them silently again, and
trimmed both llms files to the agreed docs-index shape.

**Changed:**

- **Markdown siblings advertised again**: homepage, the 5 product pages,
pricing, and blog emit absolute `.md` alternate URLs via a new
`mdAlternates(slug)` helper (the one documented consumer of the tag
parses it from `<head>` and fetches the `.md` sibling, so tags must
point at the sibling, never the page itself).
- **Drift test**: a vitest file walks `content/md/**` and asserts every
markdown-served slug's page wires the helper (or is covered by the Pages
Router `_app.tsx` mechanism, whose alternate-link wiring the test also
asserts directly so removing it fails CI too). Source-level assertions
by design: page modules can't be imported under www's vitest config.
Fails correctly when wiring is removed (verified by hiding a page and by
altering the `_app.tsx` tag).
- **Vector orphan fixed**: `content/md/vector.md` moved to
`modules/vector` matching the live route (the page previously had no
negotiation or tag, and `/modules/vector.md` 404'd); `/vector.md` now
308s to `/modules/vector.md` and the legacy `/llms/vector.txt` redirect
no longer chains.
- **llms.txt + llms-full.txt**: the `## Product Overview` sections are
gone from both, each keeps a `## Pricing` section. This deletes the
hand-maintained links array (a drift trap) and fixes the accidental
~470-page embed, shrinking llms-full.txt from ~9.8MB to ~4.9MB and
dropping the 4.1MB generated content module from that route's serverless
bundle.

**Note:** this PR is scoped to apps/www only. The docs side
(troubleshooting pages and the rest of the docs surface) is handled
separately through a consolidated manifest-gated mechanism; an earlier
troubleshooting-tag commit was reverted out of this branch to keep the
scopes clean.

<details>
<summary>Why alternate tags matter (background)</summary>

Agents ingest markdown far more efficiently than our rendered HTML: a
fraction of the tokens and no extraction step. Since #47770 removed
UA-based serving (UA sniffing broke a major AI app's fetcher and
poisoned CDN caches), markdown is served only on explicit request: a
`.md` suffix URL, an `Accept: text/markdown` header, or llms.txt. That's
the right serving model, but it makes the markdown twin invisible to any
agent that doesn't already know our URL convention, and the major AI
fetchers send browser/wildcard Accept headers, so bare URLs hand them
HTML.

The `<link rel="alternate" type="text/markdown">` head tag is the
standards-based advertisement of the sibling. It has a documented
consumer today: an agent CLI that parses the tag from `<head>` and then
fetches the `.md` sibling, which is also why the tag must point at a
real sibling URL and never at the page itself. Peer docs sites ship this
tag as table stakes. These www pages used to carry it until the
June/July marketing redesign silently dropped it; the drift test in this
PR turns that regression class into a CI failure.

</details>

## To test

Tested locally (www + docs dev servers):
- [x] `/llms.txt` renders `## Documentation` + single-link `## Pricing`,
no Product Overview
- [x] `/llms-full.txt` renders `# Supabase` → `## Pricing` → `##
Documentation`, no Product Overview, ~4.9MB
- [x] Full www suite: 6 files / 71 tests green; drift test fails
correctly when a page is removed or the `_app.tsx` wiring is altered
- [x] `generateMdContent.mjs` emits `modules/vector`, bare `vector` slug
gone

On the Vercel preview (browser-verified with Playwright):
- [x] Alternate tag present on `/`, `/auth`, `/database`, `/storage`,
`/edge-functions`, `/realtime`, `/pricing`, and a blog post: exactly one
tag each, href = preview origin + `.md` sibling
- [x] `/vector.md` → 308 → `/modules/vector.md`, renders as markdown (`#
Supabase Vector`)
- [x] `/llms.txt` shows single-link `## Pricing`, no Product Overview
- [x] Coverage sweep: all 482 `MD_PAGES` slugs + changelog index/entry
curled on the preview; 471 pages carry exactly one tag, all `.md`
siblings 200 as `text/markdown`. The 11 misses are legacy blog slugs
whose HTML 308-redirects away (stale `MD_PAGES` entries predating this
PR, no head to tag; follow-up tracked in Linear)

Post-merge prod:
- [ ] Full llms.txt link sweep (every linked URL 200s; previews can't
cover the docs-hosted links)

## Linear

- fixes GROWTH-1013


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added Markdown alternate links across key product, pricing, blog, and
troubleshooting pages.
* Added Supabase Vector documentation covering features, use cases,
workflows, and technical details.
* Updated AI-focused documentation indexes with dedicated pricing
content.
  * Added redirects for updated Vector documentation URLs.

* **Tests**
* Added coverage to verify Markdown documentation links stay aligned
with available pages.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 17:06:16 +08:00
Pamela Chia 1b1b1ea0e0 chore: remove dead telemetry code (#47950)
## Summary
Removes two pieces of dead telemetry code found while root-causing the
docs pageview re-fire investigation (GROWTH-997, closed with no fix
needed). Pure deletion, 30 lines, no behavior change.

## Changes
- Delete `apps/www/app/ConsentWrapper.tsx`: an unwired third
`PageTelemetry` mount. www already mounts `PageTelemetry` in
`pages/_app.tsx` (Pages Router) and `app/providers.tsx` (App Router);
nothing imports this wrapper.
- Remove the exported `POSTHOG_URL` constant from
`apps/studio/lib/constants/index.ts`: zero consumers. The CSP allowlist
in `apps/studio/csp.ts` defines and uses its own local `POSTHOG_URL`,
which stays.

## Testing
Deadness verified before deletion, on current master:
- [x] Repo-wide grep for `ConsentWrapper`: only self-references inside
the deleted file
- [x] Repo-wide grep for `POSTHOG_URL`: remaining references are the
`csp.ts` local const only

## Linear
- fixes GROWTH-1002


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Removed an obsolete consent-related page wrapper to streamline page
behavior.
* Updated application configuration handling without changing existing
payment or usage settings.

* **Refactor**
* Simplified internal configuration and page composition while
preserving the existing user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 12:01:29 +01:00
Pamela ChiaandAlaister Young 9858562b8b fix(telemetry): dedupe funnel toast error events (#47802)
## Summary
Since #47293, an API failure on a signup / org-creation /
project-creation form emitted `dashboard_error_created` twice:
`useTrackFunnelError` fired the origin-tagged event and the global
`ToastErrorTracker` independently fired the legacy untagged
`source:'toast'` event for the same toast, each behind its own 10%
sampling draw. I verified the twin rate empirically at 8-11% of
origin-tagged funnel toasts, exactly the floor for two independent 10%
draws, meaning the twin co-fires for effectively every funnel error
([Hex
thread](https://app.hex.tech/supabase/thread/019f3bc1-3a5c-7200-9122-8e3439bfbe8c)).
Any consumer counting funnel errors without an `origin IS NOT NULL`
filter saw ~2x inflation.

The fix makes `ToastErrorTracker` the sole emitter of `source:'toast'`
events, so the duplicate is unrepresentable rather than suppressed.
Funnel call sites pass the id returned by `toast.error()` into
`trackFunnelError`, which registers the funnel properties against that
toast id instead of firing its own event – the tracker then emits a
single `dashboard_error_created` enriched with `origin` /
`errorCategory` / `errorReason` / `errorCode` for registered toasts, and
the plain untagged event otherwise. The `'toast'` overload of
`trackFunnelError` requires the toast id, so a missed pairing is a
compile error rather than a silent double count. Registration is
unconditional and there's only one sampling draw, so suppression can't
lose a sampling race. `'form'`-sourced funnel events are unchanged.

## Changes
- `lib/toast-errors.tsx`: toast-id → funnel-properties registry
(`registerFunnelErrorToast`); `ToastErrorTracker` emits one (optionally
enriched) event per error toast under a single 10% draw, deleting
entries once consumed
- `lib/telemetry/use-track-funnel-error.ts`: overloaded signature –
`'toast'` requires the id returned by `toast.error()` (type-enforced),
`'form'` keeps direct emission with its own sampling
- Update the 7 funnel `toast.error` call sites in `NewOrgForm`,
`SignUpForm`, and `pages/new/[slug]` to pass the toast id
- Component tests for the tracker (previously uncovered), including an
end-to-end test through `useTrackFunnelError`
- Code hygiene (also flagged by CodeRabbit): all four
`dashboard_error_created` emitters (toast, form, `AlertError`,
`ErrorMatcher`) independently encoded the 10% draw – downstream analysis
assumes a uniform sampling multiplier across sources, so one site
drifting would silently skew comparisons. The rate and the draw now live
in one place (`isDashboardErrorSampled()` in
`lib/telemetry/error-sampling.ts`). No behavior change.
- Mount `ToastErrorTracker` in the TanStack root (`routes/__root.tsx`),
mirroring `pages/_app.tsx`. The TanStack tree mounted `Toaster` but
never the tracker, so untagged toast error telemetry has never fired in
that flavour – and with the tracker now the sole emitter, the missing
mount would have silently dropped funnel toast events there too. Side
effect once the TanStack flavour ships: untagged `source:'toast'` volume
from it goes from zero to normal.

## Testing
Component-tested (`apps/studio/lib/toast-errors.test.tsx`):
- [x] Unregistered error toast fires exactly one untagged
`dashboard_error_created {source:'toast'}`
- [x] Registered funnel toast fires exactly one event, enriched with
`origin`/`errorCategory`/`errorReason`/`errorCode`
- [x] `useTrackFunnelError` with a toast id routes through the tracker
as a single enriched event
- [x] Non-error toasts ignored; the 10% sampling gate still applies

Full Studio unit suite passes (392 files / 4371 tests), plus typecheck
and lint.

Also verified end-to-end in a local browser (TanStack flavour, sample
rate temporarily forced to 1): a failed signup produced exactly one
`dashboard_error_created` with `{source:'toast', origin:'signup',
errorCategory:'api', errorReason:'email_already_registered',
errorCode:403}` and no untagged twin (two independent trials); an
unregistered error toast produced exactly one plain `{source:'toast'}`;
a client-side validation failure produced exactly one `{source:'form',
origin:'signup', errorCategory:'validation',
errorReason:'email_invalid'}`; success toasts produced nothing.

Post-deploy I'll re-run the twin-rate query from the Hex thread; the
untagged-twin rate on funnel pages should decay to ~0 as stale bundles
reload over 2-3 days.

## Notes
- Origin-tagged funnel toast events now ride the tracker's single 10%
draw instead of their own independent draw – statistically identical
volume, but the event fires on the tracker's next effect rather than
synchronously at the call site (irrelevant for PostHog)
- Registration must happen in the same synchronous block as
`toast.error()` (documented on the `TrackFunnelError` type) – all
current call sites comply
- The invalid Postgres version toast in `pages/new/[slug].tsx` (~line
416) needs no special-casing: unregistered toasts keep the plain
untagged event, so its telemetry is preserved
- Heads-up for `dashboard_error_created` consumers: overall untagged
`source:'toast'` volume will dip slightly after this deploys, since
funnel-page twins disappear. A volume monitor seeing that drop is this
fix landing, not a tracking regression (same class as the intended
GROWTH-893 sampling-unification drop).

## Linear
- fixes GROWTH-965


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Enhanced error telemetry for organization creation, sign-up, payment,
and project-creation flows by associating failures with toast
identifiers and enriched funnel context.
* Standardized dashboard error sampling logic across error handling
components for consistency.

* **Tests**
* Added comprehensive test coverage for toast error tracking, including
funnel registration, deduplication, filtering, and sampling behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-07-10 17:35:27 +08:00
Pamela Chia 770f1c2b06 fix(aeo): remove ua-based markdown serving (#47770)
## Summary
The `ChatGPT-User` live-fetch agent's user-facing reader hard-fails
(`(400) OK`) on pages we serve it as markdown via user-agent matching,
which made supabase.com blog and product pages unreadable in that
assistant. I root-caused this with a controlled fetch diagnostic
cross-checked against our request logs: the failing fetches never reach
our origin (the failure is cached on their side), pages served as plain
HTML read fine everywhere we tested, and the same failure reproduces on
other major sites that serve UA-matched markdown, so the reader bug is
upstream.

This PR removes user-agent-based markdown serving entirely rather than
special-casing one agent: UA sniffing is a guess about contractless
clients whose fetchers change without notice, and this incident showed
the failure mode is silent (we keep serving 200s while the user-facing
agent breaks). Markdown remains available on every explicit signal —
`Accept: text/markdown` q-value negotiation, explicit `.md` URLs, and
llms.txt — which is the same contract-driven model the Claude fetcher
already uses successfully (it sends `Accept: text/markdown, text/html,
*/*` and keeps receiving markdown after this change).

## Changes
- Remove the `LLM_USER_AGENT` regex and the `userAgent` parameter from
`negotiateMarkdown` in `packages/common/markdown-negotiation.ts`;
decisions now depend only on `Accept`, the `.md` suffix, and the
markdown-variant manifest
- Update both consuming middlewares (`apps/www`, `apps/docs`) to the new
signature; no behavior change for Accept-negotiated or `.md` requests
- Add the missing `Vary: Accept` header to docs guides-md 200 responses
(the www `api-v2/md` route already declares it)
- Fix a pre-existing www bug surfaced in review: explicit changelog
`.md` URLs rewrote to a doubled `.md.md` path (404) under a
markdown-preferring `Accept`, and 406'd on a non-matching `Accept`. The
www middleware now strips the `.md` suffix before slug lookup and passes
`isMarkdownSuffix` into `negotiateMarkdown`, folding the separate
`MD_PAGES` `.md` block into the single negotiation path (same shape as
the docs middleware)
- Rework tests: UA-independence suites replace the per-agent rewrite
tests; a probe Accept header now 406s regardless of user agent
(previously agent UAs were exempt); new changelog `.md` negotiation
coverage

## Testing
Tested locally:
- [x] www middleware suite 36/36, docs middleware suite 17/17
- [x] typecheck green for common, www, docs

Verified on the Vercel previews (www + docs) with curl:
- [x] `ChatGPT-User` and `Claude-User` UA GETs on blog/pricing/guide
pages return `text/html` with a default Accept
- [x] Claude's real Accept (`text/markdown, text/html, */*`) still
returns `text/markdown`; `Accept: text/markdown` and `.md` URLs return
`text/markdown`; probe Accept returns 406
- [x] `/changelog/<slug>.md` with `Accept: text/markdown` returns the
entry markdown as a direct 200 (production today detours through a 308
to the bare URL); changelog index `.md` and bare-entry Accept
negotiation also verified
- [x] docs guides markdown 200s carry `Vary: Accept`

The intermediate commit (ChatGPT-User-only exclusion) was already
verified on the preview: `ChatGPT-User` got HTML while
`Accept`/`.md`/other-UA markdown was unaffected.

Expected effects post-merge: UA-driven markdown volume in the request
logs (~92% of md traffic) collapses to the Accept + `.md` baseline;
named-agent page requests return to prerendered/static serving,
reversing the extra Vercel function invocations the UA rewrite
introduced; user-facing readability in the affected assistant recovers
within ~24h as its fetch cache revalidates. The md-share dashboard gets
a dated annotation; the ratio is not comparable across this change.

## Linear
- fixes GROWTH-973


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Markdown and HTML routing now depends on the request’s `Accept` header
and `.md` links, making content negotiation more predictable.
* Requests that don’t accept available content now consistently return
`406 Not Acceptable`, even for bot-like user agents.
* Guide markdown responses now include an `Accept`-based cache variation
header to improve correct caching behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-10 13:50:34 +08:00
Pamela Chia 6dfae09b6d fix(www): postgres card art overlaps text column (#47734)
Before 
<img width="588" height="439" alt="Screenshot 2026-07-08 at 7 29 07 PM"
src="https://github.com/user-attachments/assets/8a3380d3-571f-49cd-8f32-7b38650dd0e5"
/>

After 
<img width="578" height="436" alt="Screenshot 2026-07-08 at 7 29 55 PM"
src="https://github.com/user-attachments/assets/09e74c31-e131-41d5-87da-b4518514cfb5"
/>


## Summary

On the homepage, the Postgres Database product card's elephant artwork
renders over the card's description text at every viewport ≥1280px. I
traced it to #47226 (merged June 24): standardizing the marketing
container to `section-container` (`max-w-7xl` + `xl:px-24`) capped
products-grid content at 1088px, shrinking the card to ~538px while its
fixed geometry (250px text column + 398px right-anchored square artwork
box) needs ~625px. Large monitors regressed hardest: before June 24 they
got ~676px cards and no overlap. The app-router homepage move (#47228),
the color system PR (#47288), and the artwork PNGs are all unrelated
(verified against production DOM and full diffs).

Two changes to the artwork span in `DatabaseVisual.tsx`:

1. Cap the box width at `calc(100% - 280px)` from `md` up, where 280
covers the text column's `md:max-w-[250px]` cap (in `ProductCard.tsx`'s
`isDatabase` branch) plus card padding and breathing room. The art
scales down through its existing `object-contain`. At md/lg the card is
full-width (`md:col-span-12`), so the clamp never binds and rendering is
unchanged there.
2. Remove the `xl:-right-12` bleed (base `right-0` stands, matching how
2xl already rendered). The bleed used to clip only the art's transparent
canvas margin; with the clamped box the art fills its full width, so the
48px offset was cropping the elephant itself at 1280-1535px.

The hover line-art SVG scales with the box and stays aligned with the
PNG (identical aspect ratios: viewBox 390:430, PNG 585x645).

## Changes
- Add `md:max-w-[calc(100%-280px)]` to the artwork span in
`DatabaseVisual.tsx` so the Postgres card art can never cross its text
column
- Drop `xl:-right-12 2xl:right-0` from the same span so the smaller art
isn't clipped at the card's right edge

## Testing

Round 1 on the Vercel preview (commit e1ca671, measured via
getBoundingClientRect + computed styles):
- [x] Art clear of the text column at 1280/1440/1600/1920 (gap 54px at
1280/1440, 6px at 1600/1920); computed `max-width: calc(100% - 280px)`
applies
- [x] Hover at 1440 — SVG and PNG rects identical (pixel-exact
alignment)
- [x] 768/1024 — full-width card unchanged, span still a 398px square
(clamp resolves but doesn't bind)
- [x] Light theme at 1440 — same geometry, no overlap

Round 1 also surfaced that the `xl:-right-12` bleed now cropped the
elephant at 1280-1535px widths → second commit removes it. Round 2
(commit e28b408):
- [x] 1280/1440/1512/1600/1920 — art fully visible (span right edge
flush at the card's inner edge, e.g. 713 vs 714 at 1440), still clear of
the text column (span left 457 vs text right 451)
- [x] 768/1024 — unchanged (span still a right-anchored 398px square)
- [x] Hover alignment still exact (PNG and SVG rects identical:
457,452,256,398)

## Linear
- fixes GROWTH-971
2026-07-08 19:56:12 +08:00
Pamela Chia 0099ad1aec fix(account): stop sb marker leaking into email toast (#47455)
## Summary

The email-change confirmation toast rendered a trailing `&sb=` ("...sent
to the other email&sb="). The dashboard parsed the auth-redirect URL
fragment with a naive `split('#message=')` that grabbed everything after
the key, including the empty `sb` origin marker the auth service appends
to every redirect fragment (an intentional, server-side Supabase-Auth
identifier so clients can tell a Supabase redirect from a third-party
OAuth one). The marker is working as designed; the bug is that the
dashboard wasn't parsing the fragment as URL params, so I fixed the
parse rather than the marker.

## Changes

- Parse the redirect fragment with `URLSearchParams` via a new
`parseRedirectMessage` helper, reading only the `message` key. Any other
trailing fragment param (the `sb` marker, or future ones) is now ignored
instead of being concatenated into the toast.
- Drop the manual `+`-to-space replacement. `URLSearchParams.get()`
already decodes form-encoded values, and the old `.replaceAll('+', ' ')`
would have clobbered a legitimately encoded `+`.
- Add unit tests for the helper: marker stripped, no hash, no `message`
key, `message` not first, and percent-encoded `+` preserved.

## Testing (Vercel preview)

The toast only reads the URL fragment, so the redirect can be simulated
directly. Do not use the real email round-trip on the preview: a real
confirm-link click is redirected to prod (the backend sets
`redirect_to`), not the preview build.

- [x] On the preview, log in and open the account preferences page with
this fragment appended:
`/account/me#message=Confirmation+link+accepted.+Please+proceed+to+confirm+link+sent+to+the+other+email&sb=`
— toast shows the clean sentence with no `&sb=`.
- [x] Open the same page with no fragment — no toast fires.

## Linear

- fixes GROWTH-938


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved how success messages are read after redirect in account
identity preferences, so notifications now display the correct text more
reliably.
* Supported messages with spaces and special characters, including cases
where the message appears later in the URL fragment.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-01 15:24:51 +08:00
Pamela Chia 98cfe3307e feat(telemetry): fix creation-funnel tracking gaps (#47386)
## Summary

The creation-funnel instrumentation that shipped Jun 25 (#47291, #47293)
had real gaps, surfaced by the weekly telemetry audit and confirmed
against production PostHog data before I touched code. The two automated
reports also contradicted each other on `errorReason`; I checked
production (every value is a controlled slug) and the emit path (only
`useTrackFunnelError` sets it, and it only accepts classified slugs), so
I left the type as-is rather than add a cross-package abstraction for a
risk that cannot occur today.

## Changes

- Classify HTTP 401/403/404 API errors as `unauthorized` / `forbidden` /
`not_found` instead of the catch-all `other`. In production the
`org_creation` `other` bucket was ~96% 401s (~1,300 real over 4 days),
invisible in reason breakdowns. The status-code fallback runs after the
message-pattern match, so specific reasons still win and it only rescues
errors that would otherwise be `other`.
- Add a single `tier` property (`tier_free` / `tier_pro` / `tier_payg` /
`tier_team`) to `organization_creation_completed`, which previously
carried no properties. One canonical billing slug (matching
`SubscriptionTier`) instead of two overlapping plan/tier fields, so the
org-creation funnel segments cleanly by tier and joins against
subscription data. `tier_payg` is uncapped PRO.
- Freeze the submitted tier at submit time (snapshot in `createOrg`)
rather than reading live form state in the success callback, so the
event records the tier that was actually created even if the user edits
the form during the async payment flow.
- Emit `project_creation_form_exposed` with `surface: 'vercel'` on the
integration deploy-button project-creation page (the enum value existed
but was never fired). Gated on the URL `slug` so the impression is
captured as soon as the form renders, matching the sibling exposure hook
on that page.

I also checked the confirm-modal error path flagged in the insights
post: it already classifies via the shared
`useProjectCreateMutation.onError`, so adding instrumentation there
would double-count. No change made.

## Testing

These are analytics events with no UI change, so correctness is in what
lands in PostHog. Post-deploy validation I will run against production
(project 34344):

- `dashboard_error_created` where `origin='org_creation'` and
`errorReason='other'` drops ~96%, with `unauthorized` / `not_found`
appearing.
- `organization_creation_completed.tier` populated on 100% of new events
with one of the four tier slugs.
- `project_creation_form_exposed` with `surface='vercel'` goes from 0 to
greater than 0.

## Linear

- fixes GROWTH-948


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added telemetry for organization creation completion that includes the
selected billing tier.
* Added one-time telemetry when the Vercel project creation form is
exposed.
* **Bug Fixes**
* Improved API error classification to more accurately distinguish
unauthorized, forbidden, and not found responses.
* **Documentation**
* Updated telemetry event definitions to require tier metadata for
organization creation events.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 23:11:53 +08:00
Pamela ChiaandAlaister Young 20290c71bd fix(docs): stop named-bot markdown 404s on guides (#47337)
## Summary

Since the guides UA-redirect shipped (GROWTH-811), named LLM bots
requesting `/docs/guides/*` get rewritten to the markdown handler, which
returns a 404 when no `.md` file exists. About 90K of those 404s per day
land on real pages that serve HTML 200 fine: the bot gets nothing on a
page that works.

The root cause is that the docs middleware hardcoded
`hasMarkdownVariant: true` for every guide path, so it never checked
whether a `.md` actually existed. I fixed it in two layers:

1. A build-time slug manifest makes `hasMarkdownVariant` truthful. Guide
pages with no `.md` now fall through to HTML 200 instead of a 404. This
is content-source-agnostic and future-proof: a new content source can
never silently regress to a 404.
2. A second generator pass emits real markdown for the troubleshooting
collection (the largest source, ~70% of the 404 volume), so those bots
get clean markdown rather than just HTML.

## Changes

- Add a shared `markdown-sources` module: a single source of truth for
which slugs get a `.md` (guides + troubleshooting), so the generator
output and the manifest cannot drift.
- Generate markdown for the troubleshooting collection (196 pages, TOML
frontmatter parsed via `smol-toml`), written under
`public/markdown/guides/troubleshooting/`.
- Emit a build-time slug manifest (a gitignored generated `.ts` module,
regenerated in `prebuild`, `predev`, and `pretypecheck`, mirroring the
existing `__generated__/graphql.ts` lifecycle).
- Gate the middleware's `hasMarkdownVariant` on the manifest: serve HTML
200 instead of a 404 for guide paths with no markdown variant.

This PR intentionally does not generate markdown for the ai-prompts,
YAML config, and externally-fetched (splinter) sources. The HTML
fallback covers them now; generating their markdown is follow-up work.

## Testing

Local verification (deterministic, against the real manifest and the
real negotiation function):
- Manifest invariant holds: 744 manifest slugs equal 744 generated `.md`
files.
- Generator emits 196 troubleshooting files with zero warnings,
frontmatter stripped, no leaked delimiters.
- Negotiation decision matrix, 6/6: covered slug + bot UA to markdown;
uncovered real page + bot UA to pass (HTML 200); nonexistent + bot UA to
pass; browser to HTML; covered + `.md` suffix to markdown; uncovered +
`.md` suffix to pass.

Verified on the Vercel preview deploy:
- [x] `User-Agent: ChatGPT-User` on a troubleshooting page returns `200
text/markdown` (real markdown body, frontmatter stripped).
- [x] `User-Agent: ChatGPT-User` on an uncovered real page
(`ai-tools/ai-prompts/code-format-sql`) returns `200 text/html` (was
404).
- [x] Browser request to the same uncovered page returns `200 text/html`
(unchanged for humans).
- [x] `User-Agent: ChatGPT-User` on a covered standard guide returns
`200 text/markdown` (no regression).
- [x] `User-Agent: ChatGPT-User` on a nonexistent guide URL returns
`404` (correct).

Known limitation: an explicit `.md`-suffix request on an uncovered page
still 404s by design (an explicit markdown request for a page that has
no markdown). The ~90K/day volume is plain-URL UA-based, so it is
unaffected.

Post-deploy, I will re-run the request-grain 404 reclassification in the
GROWTH-915 BQ workspace to confirm fixable guide markdown 404s drop to
near zero.

## Linear
- fixes GROWTH-946


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added generated markdown slug tracking for docs guides, improving
markdown availability detection.
* Added automated manifest generation and validation during docs build
and CI workflows.

* **Bug Fixes**
* Improved guide markdown negotiation so only supported guide slugs are
treated as having a markdown variant.
* Standardized markdown source handling for guides and troubleshooting
pages.

* **Tests**
  * Added coverage for guide and troubleshooting slug generation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-06-27 22:00:14 -07:00
Pamela Chia 0a797ef4ea feat(studio): add creation funnel telemetry (#47291)
## 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 -->
2026-06-25 13:17:11 +00:00
Pamela Chia 2d0bcd4714 feat(telemetry): classify funnel creation errors (#47293)
## 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 -->
2026-06-25 20:56:23 +08:00
Pamela Chia d549e1f16b fix(billing): clarify averaged usage messaging (#47181) 2026-06-23 18:10:17 +08:00
Pamela Chia 4c636e2074 fix(studio): track homepage service-card clicks (#47210) 2026-06-23 18:09:31 +08:00
Pamela Chia af9e531abc chore(docs): gitignore extracted public/docs/ markdown (#47208) 2026-06-23 18:09:13 +08:00
Pamela Chia dc5ddd8c4c chore(telemetry): align event interface names with action strings (#47048)
## Summary

Aligns 16 telemetry event interface identifiers in
`packages/common/telemetry-constants.ts` so each interface name equals
the PascalCase of its `action` string (`PascalCase(action) + Event`).
This is a follow-up to the GROWTH-798 audit (#45964), which fixed the
`action` strings and several interface names but left these 16
identifiers mismatched. I renamed identifiers only: every `action`
string is untouched, so there is zero impact on PostHog event names or
historical data.

Before this change, 176/192 interfaces matched the convention. This
brings it to 192/192.

## Changes

Structural renames (interface name was dropping or reordering words vs
the action):
- `AskAIEvent` to `AskAiClickedEvent`
- `CopyAsMarkdownEvent` to `CopyAsMarkdownClickedEvent`
- `DocsRecommendation404ClickedEvent` to
`Docs404RecommendationClickedEvent` (from #46990)
- `EventPageCtaClickedEvent` to `WwwEventPageCtaClickedEvent` (completes
the interface side of GROWTH-798 HIGH #1)
- `ImportDataFileAddedEvent` to `ImportDataDropzoneFileAddedEvent` (also
updates the consumer `apps/studio/hooks/ui/useCsvFileDrop.ts`)
- `QueryPerformanceAIExplanationButtonClickedEvent` to
`QueryPerformanceExplainWithAiButtonClickedEvent`

Initialism casing (normalized to the file-majority lowercase transform;
`Sql` 9:2, `Api` 3:2, `Ai` 6:2):
- `CustomReportAddSQLBlockClickedEvent` to
`CustomReportAddSqlBlockClickedEvent`
- `CustomReportAssistantSQLBlockAddedEvent` to
`CustomReportAssistantSqlBlockAddedEvent`
- `HomepageGitHubButtonClickedEvent` to
`HomepageGithubButtonClickedEvent`
- `MetricsAPIBannerCtaButtonClickedEvent` to
`MetricsApiBannerCtaButtonClickedEvent`
- `MetricsAPIBannerDismissButtonClickedEvent` to
`MetricsApiBannerDismissButtonClickedEvent`
- `TableRLSEnabledEvent` to `TableRlsEnabledEvent`
- `RLSGeneratePoliciesClickedEvent` to `RlsGeneratePoliciesClickedEvent`
- `RLSGeneratedPolicyRemovedEvent` to `RlsGeneratedPolicyRemovedEvent`
- `RLSGeneratedPoliciesCreatedEvent` to
`RlsGeneratedPoliciesCreatedEvent`
- `RLSTesterRunQueryClickedEvent` to `RlsTesterRunQueryClickedEvent`

## Testing

Type-only change, no runtime or PostHog behavior to exercise. Verified
that all 192 interfaces now match `PascalCase(action) + Event` (0
mismatches), the `TelemetryEvent` union has no duplicates, no old
identifier names remain anywhere in the repo, and the one external
consumer (`useCsvFileDrop.ts`) still resolves via its `['action']`
indexed access since the action strings are unchanged.

## Linear

- fixes GROWTH-928


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated internal telemetry infrastructure for consistency and
maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-17 23:31:03 +08:00
Pamela Chia 91982e6a2d feat(studio): track unified logs cta variant and auto-dismiss on explore (#46940)
## Summary
The Unified Logs promo banner (shipped in #46847) had two telemetry/UX
gaps I found while auditing the weekly PostHog event review. Its CTA
fired one event for two different user paths with no way to tell them
apart, and clicking "Explore" left the banner in place. This adds an
`is_enabled` property to the CTA event and auto-dismisses the banner on
the Explore path.

## Changes
- Add `is_enabled: boolean` to `unified_logs_banner_cta_button_clicked`.
It is `true` when the user is already enabled and the button navigates
to the logs page ("Explore"), and `false` when not enabled and the
button opens the feature-preview modal ("Enable"). The two cohorts are
now queryable independently, which is what makes the CTA data usable for
measuring adoption.
- Auto-dismiss the banner when an already-enabled user clicks "Explore".
Previously only the X button dismissed it, so an Explore click left the
banner showing on the next project page load. Scoped to the Explore path
on purpose: the not-enabled path only opens a preview modal (it does not
enable), so dismissing there would hide the banner from users who never
enabled.

## Testing
Behavior to verify on the Vercel preview:
- [x] Enabled user clicks "Explore Unified Logs": navigates to the logs
page, banner does not reappear on the next project page load, CTA event
fires with `is_enabled` true.
- [x] Non-enabled user clicks "Enable Unified Logs": preview modal
opens, banner is still present after closing the modal, CTA event fires
with `is_enabled` false.
- [x] X button: banner dismissed as before, dismiss event fires.

Out of scope on purpose: no impression event (it would fire on every
banner render, low-millions of events per month for one banner), so true
click-through rate stays unmeasurable for now.

## Linear
- fixes GROWTH-925


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Improvements**
* Unified Logs banner now intelligently updates behavior based on
feature state.
* When enabled, exploring the feature automatically dismisses the
banner.
  * When disabled, the enable action opens the feature preview flow.
  * Enhanced tracking for banner interactions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-15 15:07:03 +00:00
Pamela Chia cd52669f1f fix(docs): negotiate /guides/* markdown via shared helper (#45432)
## Summary

This brings docs `/guides/*` to full content negotiation for AI agents
(GROWTH-811):
RFC 9110 q-value parsing instead of a `.includes('text/markdown')`
substring match,
a 406 when the client rejects every type the route can produce, and
markdown rewrites
for known LLM user agents.

I implemented it by extracting the negotiation into a shared
`common/markdown-negotiation`
module consumed by both `apps/docs/middleware.ts` and
`apps/www/middleware.ts`, rather than
duplicating the helpers into docs and keeping them in sync by hand with
www (#45394). Single
source of truth, no re-sync burden. www is refactored onto the shared
helper with no behavior
change.

## Changes

### docs `/guides/*` content negotiation (GROWTH-811)

- Replace the `.includes('text/markdown')` substring match with RFC 9110
q-value parsing.
- Return 406 (`Cache-Control: no-store`, `Vary: Accept`) when Accept
excludes every type the
route serves. Bypassed for LLM user agents, the `.md` suffix, and
clients sending no Accept.
- Rewrite to `/api/guides-md/<slug>` for LLM user agents (Claude-User,
Claude-Web, ChatGPT-User,
  PerplexityBot) regardless of Accept.
- Preserve the existing `.md` suffix routing and the entire
`/reference/*` block.

### Shared negotiation helper

- New `packages/common/markdown-negotiation.ts`:
`negotiateMarkdown(signals, route)` returns
`'markdown' | 'not-acceptable' | 'pass'`. Internalizes q-value parsing,
the LLM user-agent
  match, the UA-length cap, and the markdown-vs-html preference.
- `apps/www/middleware.ts`: refactored to consume the shared helper; its
duplicated copy of the
negotiation helpers (added in #45394) is removed. `.md` early-return,
changelog routing, and
first-referrer cookie stamping are unchanged (no behavior change,
covered by its existing tests).

### Tests

- New `apps/docs/middleware.test.ts`: q-value priority, the 406 path,
`.md` suffix, LLM UA
override, browser default Accept, training-crawler and substring-embed
exclusion, and the
  `/reference/*` exemption.
- New `packages/common/markdown-negotiation.test.ts`: the same decision
matrix at the unit level
(q-values, 406, LLM UAs, `.md`, `*/*`, training crawlers, OWS,
out-of-range q).

## Testing (Vercel preview)

After Vercel posts a preview URL, save it once then run the probe set.

```bash
echo 'PREVIEW_HOST' > /tmp/growth-811-host.txt
HOST=$(cat /tmp/growth-811-host.txt)

# 1) Browser-style Accept -> HTML 200
curl -sI -A "Mozilla/5.0" \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' \
  "https://$HOST/docs/guides/auth"

# 2) Accept: text/markdown -> markdown 200
curl -sI -H 'Accept: text/markdown' "https://$HOST/docs/guides/auth"

# 3) text/html;q=1.0, text/markdown;q=0.5 -> HTML 200
curl -sI -H 'Accept: text/html;q=1.0, text/markdown;q=0.5' "https://$HOST/docs/guides/auth"

# 4) unsupported Accept -> 406 + Cache-Control: no-store + Vary: Accept
curl -sI -H 'Accept: application/x-content-negotiation-probe' "https://$HOST/docs/guides/auth"

# 5) User-Agent: Claude-User/1.0 (any Accept) -> markdown 200
curl -sI -A 'Claude-User/1.0' "https://$HOST/docs/guides/auth"
```

### After merge

Run
[acceptmarkdown.com/readiness-check](https://acceptmarkdown.com/readiness-check)
against `https://supabase.com/docs/guides/auth`: expect 100/100.

## Linear

- fixes GROWTH-811
2026-06-09 17:49:35 +08:00
Pamela Chia bf81e46173 chore: update DPA to June 2026 version (#46558) 2026-06-05 17:29:40 +08:00
Pamela Chia f8a3a2e28c feat(billing): label logs ingest/query in restriction banners (#46609) 2026-06-03 21:07:45 +08:00
Pamela Chia 7f5e3cab93 chore(studio): remove expired Fly.io deprecation banner (#46535) 2026-06-01 19:35:14 +08:00
Pamela Chia 9da249135c fix(telemetry): fix and unify dashboard_error_created tracking (#46537) 2026-06-01 19:34:56 +08:00
Pamela Chia b42bebf4c9 chore(llms): consolidate /llms.txt to single root path (#46468)
## Summary

Removes the preemptive 301 redirects from `/docs/llms.txt` and
`/docs/llms-full.txt` to root, and switches the docs homepage
`rel=alternate` to an absolute URL. The `/docs/*` paths never had a
producer in `apps/docs` (no route handler, no static file at
`public/llms.txt` or `public/llms-full.txt`); the redirects were cruft
from before the root paths were canonical. Now `/docs/llms*` cleanly
404s and the only advertised path is the root canonical per llmstxt.org.

## Changes

- Delete `/docs/llms.txt` and `/docs/llms-full.txt` 301 entries from
`apps/www/lib/redirects.js`
- Switch `apps/docs/app/page.tsx` rel=alternate `text/markdown` from
relative `/llms-full.txt` to absolute
`https://supabase.com/llms-full.txt`, so `basePath: '/docs'` does not
reconstruct the now-dead `/docs/llms-full.txt` path

## Testing

Verify on Vercel preview:

- [ ] `curl -sI -L <preview>/docs/llms.txt` returns 404 (no producer,
redirect removed)
- [ ] `curl -sI -L <preview>/docs/llms-full.txt` returns 404
- [ ] `curl -sI -L <preview>/llms.txt` returns 200 with `content-type:
text/plain`
- [ ] `curl -sI -L <preview>/llms-full.txt` returns 200, ~9 MB body
- [ ] `curl -s <preview>/docs/ | grep 'rel="alternate".*llms-full'`
shows the absolute `https://supabase.com/llms-full.txt` href (no
`/docs/` prefix)

Caveat: the absolute alternate URL points at production from preview
deploys. Acceptable because Vercel previews are `noindex` by default and
the existing `canonical: BASE_PATH` already crosses environments via
basePath resolution.

## Linear

- fixes GROWTH-881


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated documentation URL references to use absolute Supabase-hosted
paths
* Reorganized legacy product documentation redirects to new
markdown-based routes

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46468?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-29 13:08:16 +08:00
Pamela Chia 43bacd2f70 fix(www): use service role key for LWX edge function storage uploads (#46411)
## Summary

The three LWX edge functions consume `MISC_USE_ANON_KEY` while their
inline comments claim "SERVICE ROLE KEY": a copy-paste bug. This PR
swaps them to `MISC_USE_SERVICE_ROLE_KEY`, matching `lw11-og` (the
reference implementation that already uses the service role pattern
correctly). The secret is already configured on the project. Service
role bypasses RLS, so uploads succeed regardless of bucket policy and
the key is never exposed (edge functions are server-side).

## Changes

- `lwx-ticket/handler.tsx`: swap storage client to use
`MISC_USE_SERVICE_ROLE_KEY`
- `lwx-og/handler.tsx`: swap storage client to use
`MISC_USE_SERVICE_ROLE_KEY`
- `lwx-ticket-og/handler.tsx`: swap storage client to use
`MISC_USE_SERVICE_ROLE_KEY`

## Testing (Vercel preview)

1. Hit an LWX ticket share URL with a Twitterbot UA: `curl -A
'Twitterbot/1.0' '<preview>/launch-week/x/tickets/<username>'` — expect
HTML with OG meta tags, no edge function error.
2. Confirm the storage object got upserted at
`images/lwx/og/<type>/<username>.png` on `obuldanrptloktxcffvn`.
3. Confirm `MISC_USE_SERVICE_ROLE_KEY` is set as an edge function secret
on the project (it already powers `lw11-og`, so it should be).

### Follow-up (separate, manual)

After merge and deploy, the storage policy on the `images` bucket needs
tightening via the dashboard (no migration setup exists for this
project): revoke `INSERT`/`UPDATE`/`DELETE` policies that grant `anon`,
keep `SELECT` public. All legitimate writers (dashboard team uploads,
LWX/LW11 edge functions) use service role and will continue to work.

## Linear

- fixes GROWTH-882


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated backend authentication configuration for image generation and
ticket-related operations.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46411?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-27 15:36:24 +08:00
Pamela Chia 47c084e51d refactor(studio): migrate telemetry to useTrack (#46140)
## Summary

I migrated every `useSendEventMutation` call site in `apps/studio` to
`useTrack`, deleted the legacy hook, and added a lint guardrail so it
can't return. `useTrack` is the type-safe replacement: it auto-injects
`groups: { project, organization }` from the selected project/org and
types `action` + `properties` against `TelemetryEvent`. Existing call
sites built groups manually and were not type-checked at the action
level. The migration covers 81 files (60 trivial swaps, 9 org-only, 3
pre-auth, 5 bespoke, 4 test mocks).

## Changes

- Migrated trivial call sites across `pages/project/[ref]`,
`components/interfaces/*` (Reports, Storage, Realtime/Inspector,
SQLEditor, Functions, EdgeFunctions, Integrations, ProjectAPIDocs,
Branching/BranchManagement, TableGridEditor, Connect, Docs, Auth,
Support, Home, ProjectHome, App), `components/layouts/*`, and
`components/ui/*`.
- Migrated org-only sites (`Organization/Documents/*`,
`Organization/BillingSettings/Subscription/*`,
`Organization/SecuritySettings.tsx`,
`Account/Preferences/DashboardSettingsToggles.tsx`) by dropping the
manual `groups: { organization: ... }` and letting `useTrack`
auto-inject. Verified `useSelectedProjectQuery` is disabled on org
routes (gates on URL `[ref]`).
- Migrated pre-auth sites (`SignInForm.tsx`, `sign-in-mfa.tsx`,
`profile.tsx`) where neither project nor org is resolved.
- Bespoke handling:
- `execute-sql-mutation.ts` and `table-row-create-mutation.ts`: pass `{
project: projectRef }` via `groupOverrides` since the mutation can
target a non-selected project ref.
- `useStudioCommandMenuTelemetry.ts`: kept a direct `sendTelemetryEvent`
call because studio groups must override pre-built event groups
(opposite of `useTrack`'s override direction).
- `AIAssistantOption.tsx`: passes sentinel-aware `groupOverrides` so
`NO_PROJECT_MARKER`/`NO_ORG_MARKER` continue to suppress group emission.
- `SidePanelEditor.utils.tsx`: utility functions `createTable` and
`updateTable` now take a `track: Track` parameter (threaded from
`SidePanelEditor.tsx`); dropped the `organizationSlug` arg since groups
are no longer assembled manually.
- Branch-event attribution: preserved `parentProjectRef` overrides on
`branch_updated`, `branch_merge_completed`, `branch_merge_failed`,
`branch_merge_submitted`, `branch_delete_button_clicked`,
`branch_review_with_assistant_clicked`, and
`branch_*_merge_request_button_clicked`. Original code grouped these
under the parent (production) project, not the branch ref;
auto-injection would have shifted them onto the branch.
- Switched 4 test mocks from `@/data/telemetry/send-event-mutation` to
`@/lib/telemetry/track`. Removed obsolete tests around manual groups and
`try/catch` on telemetry rejection.
- Deleted `apps/studio/data/telemetry/send-event-mutation.ts`. The
deleted module is its own guardrail: any reintroduction of the import
fails at TypeScript module resolution before lint runs.

## Testing

Tested on preview deploy:

- [x] SQL editor `CREATE TABLE` fires `table_created` with method
`sql_editor` and `groups.project` set to the mutation's `projectRef`.
- [x] Table editor creates a table from the side panel; `table_created`
fires from `SidePanelEditor.utils` via threaded `track`.
- [x] Help button (`/project/[ref]/...`) fires `help_button_clicked`
with auto-injected project + org groups.
- [x] Sign-in form fires `sign_in` with empty groups (pre-auth,
expected).
- [x] Org documents page (`/org/[slug]/documents`) fires
`document_view_button_clicked` with org group only, no stale project
ref.
- [x] Command menu (`Cmd+K`) inside a project still fires
`command_menu_opened` with studio's project/org overriding any
event-supplied groups.
- [x] Support form "Ask the Assistant" without selected org fires
`ai_assistant_in_support_form_clicked` with no project/org groups
(sentinels suppress).
- [x] On a branch, "Update branch" / "Merge branch" / "Close merge
request" events fire with `groups.project` set to the parent project
ref, not the branch ref.

Local checks:
- [x] 22/22 tests pass across the 4 updated test files
(`SidePanelEditor.utils.createTable`, `EdgeFunctionRenderer`,
`LayoutSidebar`, `PlanUpdateSidePanel`).
- [x] `rg useSendEventMutation apps/studio` returns 0 hits.

## Linear
- fixes GROWTH-860


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Standardized telemetry across the Studio to a unified tracking system;
events now send simplified payloads with less contextual/grouping data.
* No user-facing flows changed; UI behavior, permissions, and
interactions remain the same.
* **Tests**
* Updated telemetry mocks and tests to align with the new tracking
approach.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46140?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-27 15:19:54 +08:00
Pamela Chia 0bed80b340 chore(telemetry): clean up frontend event catalog (#45964)
## Summary
Resolves 13 findings (2 HIGH, 5 MEDIUM, 6 LOW) from the frontend
telemetry audit: 1 action-string collision, 1 camelCase experiment name,
9 dead events removed, 4 missing org groups attached, 1 ambiguous
property renamed, 1 raw-string property narrowed, plus consolidations
and a structural tightening on TABLE_EVENT_ACTIONS.

## Changes
### HIGH
- Rename `EventPageCtaClickedEvent.action` to
`www_event_page_cta_clicked` so it no longer collides with the pricing
CTA event (which had a different schema sharing the same action string)
- Snake_case the header-upgrade experiment exposure name
(`headerUpgradeCta_experiment_exposed` →
`header_upgrade_cta_experiment_exposed`); PostHog flag key and
`?source=` URL param unchanged

### MEDIUM
- Remove 4 dead `ProjectCreation*Step*` events (referenced a v2 route
that doesn't exist; 0 emissions)
- Remove 4 dead experiment exposure events:
`ProjectCreationRlsOptionExperimentExposed`, `HomeNewExperimentExposed`,
`TableCreateGeneratePoliciesExperimentExposed`,
`TableCreateGeneratePoliciesExperimentConverted` (0 emissions)
- Attach org group to `dpa_request_button_clicked` (0% had `$group_0`
per Hex)
- Delete `RegisterStateOfStartups2025NewsletterClicked` (interface
naming outlier, 0 emissions, page renamed to 2026)
- Rename `AssistantSuggestionRunQueryClickedEvent.category` to
`mutationType` with tightened literal union (`'functions' |
'rls-policies' | 'unknown'`)
- Attach org group to `project_creation_default_privileges_exposed` on
Vercel surface via explicit `groupOverrides` (auto-injection misses
because `useSelectedOrganizationQuery` is undefined on that page)

### LOW
- Consolidate `IndexAdvisorBannerEnableButtonClickedEvent` +
`IndexAdvisorDialogEnableButtonClickedEvent` into one event with
`origin: 'banner' | 'dialog'`
- Rename `ImportDataFileDroppedEvent` → `ImportDataFileAddedEvent` so
the interface name matches the action and the verb is on the approved
list
- Rename `LogDrainConfirmButtonSubmittedEvent` → `LogDrainRemovedEvent`
and action to `log_drain_removed` (fires on delete-confirm modal,
matches `CronJobRemovedEvent` pattern)
- Add `type` property to `CronJobRemovedEvent` (parsed from the job's
command), matching the create/update event shape
- Tighten `TABLE_EVENT_ACTIONS` values with `satisfies` against the
event union so renames in the union fail typecheck here too
- Attach org group to `www_pricing_plan_cta_clicked` at 5 emission sites
when an org is available in the page context
- Narrow `unified_logs_row_clicked.logType` from raw `string` to the
5-literal `LOG_TYPES` union (zod already validates server values)

### Bundled refactor
Migrated 5 emission sites from deprecated `useSendEventMutation` to
`useTrack` while their containing files were being edited: `DPA.tsx`,
`DisplayBlockRenderer.tsx`, `Grid.tsx` (2 events), `DeleteCronJob.tsx`.
Full sweep of the remaining ~79 files is a separate follow-up.

## Testing

Mostly just renaming of events

## Linear
- fixes GROWTH-798


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Standardized telemetry to a unified tracking system for more
consistent analytics.
  * Simplified experiment exposure reporting for upgrade prompts.

* **New Features**
* More granular tracking for CSV import, cron job deletions, log drain
removals, DPA downloads/requests, and pricing CTAs.
  * Assistant now classifies mutation queries more precisely.

* **Bug Fixes**
* Improved default-privileges exposure logic on Vercel deployments
(skips when org missing).

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45964)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-18 18:21:58 +08:00
Pamela Chia e55411da5e feat(studio): Fly.io deprecation banner (#45778)
## Summary

Adding an in-dashboard banner for the Fly.io May 31 suspension. Banner
targets users on a Fly project (or with a Fly project in their
currently-selected org) and surfaces a per-project breakdown of what's
affected in a dialog. Detection is self-correcting: as soon as the user
migrates off Fly, the banner disappears with no follow-up.

<img width="557" height="502" alt="Screenshot 2026-05-11 at 5 08 22 PM"
src="https://github.com/user-attachments/assets/7bafb712-3490-4555-9667-66e9909f1b1a"
/>
<img width="1675" height="536" alt="Screenshot 2026-05-11 at 3 55 06 PM"
src="https://github.com/user-attachments/assets/6c1bf9d1-4dcc-4aac-a679-2ed477d2ed1c"
/>


## Changes

- **Detection hook** (`useFlyDeprecationProjects`): reads only from
already-cached data — `useSelectedProjectQuery` for the current project,
plus `useOrgProjectsInfiniteQuery` scoped to the selected org. Zero
cross-org fan-out: worst case is one paginated query per session (the
same one the project list page already makes).
- **Banner component** (`FlyDeprecationBanner.tsx`): mounted in
`AppBannerWrapper`. Dynamic title (primaries / branches / both), dialog
lists affected projects with org name, numbered migration steps, links
to backup/restore CLI + Dashboard backup + branching docs. List
truncates to 5 entries with "…and N more." tail when more are affected.
- **Telemetry**: `fly_deprecation_banner_exposed` and
`fly_deprecation_banner_dismissed` events emitted via `useTrack`
(auto-injects project + org groups). Properties: `primaryCount`,
`branchCount`. CTA click tracking intentionally omitted — migration
outcome is measured via warehouse `cloud_provider = 'FLY'` decay.
- **LocalStorage**: dated dismissal key `FLY_DEPRECATION_2026_05_31`;
orphan `FLY_POSTGRES_DEPRECATION_WARNING` from PR #33510 removed in the
same change so users who dismissed the Feb 2025 banner still see this
one.
- **Support contact**: email `success@supabase.io` only (no support
ticket link), per Brian's outreach copy in the Linear issues.

## Coverage trade-off

Banner renders on project pages (selected-project check) and pages where
the selected org's projects list is cached (org overview, project list).
It does **not** render on `/dashboard` home or other pages without org
context. Email outreach from GROWTH-817 / GROWTH-819 handles those
users. This was a deliberate trade-off to avoid cross-org fan-out load.

## Lifecycle

Banner expires `2026-06-01T00:00:00Z` (right after the May 31 deadline).
Stale client bundles stop rendering it without a redeploy. Cleanup PR
planned post-deadline to remove the component, hook, localStorage key,
and telemetry events.

## Testing

Tested on the Vercel preview with React Query cache overrides to mock a
Fly project:

- [x] Banner renders for a user with at least one project where
`cloud_provider === 'FLY'`
- [x] Banner does **not** render for a user with no Fly projects
- [x] Banner does **not** render on `/sign-in`
- [x] Title varies by primaries-only / branches-only / both
- [x] Dialog lists affected projects with org name in parens
- [x] Dialog list truncates to 5 with "…and N more." for larger sets
- [x] Migration guide / Dashboard backup / branching links open in a new
tab
- [x] Dismiss (×) closes the banner and persists across hard reload
(localStorage `fly-deprecation-2026-05-31-dismissed`)
- [x] PostHog receives one `fly_deprecation_banner_exposed` per mount
with `primaryCount` + `branchCount` and `$groups.organization` populated
- [x] PostHog receives one `fly_deprecation_banner_dismissed` on close
with the same property shape

## Linear

- fixes GROWTH-817
- fixes GROWTH-819
2026-05-12 02:24:47 +08:00
Pamela Chia 95d1e8abe8 fix(www): drop malformed legal URLs from sitemap_www.xml (#45775)
## Summary
Google Search Console flagged 4 "URL not allowed" errors on
`sitemap_www.xml` — malformed URLs like
`https://supabase.comdata/legal/terms/v1` (missing slash, non-existent
path). The generator was globbing `data/**/*.mdx`, picking up the 4
content-source MDX files under `data/legal/` that are imported by
`pages/terms.tsx` and `pages/enterprise-terms.tsx` but are not
themselves routed. With no path replacement mapping `data/...` to a
route and no leading slash, the URL template concatenated to garbage.
The real `/terms` and `/enterprise-terms` URLs come from the
`pages/*.tsx` glob and are unaffected.

## Changes
- Remove `data/**/*.mdx` glob (and its companion `!data/*.mdx` exclude)
from the sitemap generator. `apps/www/data/` has no routed MDX, only
content sources imported into pages.
- Anchor the `pages` prefix replace: `.replace('pages', '')` →
`.replace(/^pages/, '')`. String-form replace is first-occurrence and
would mangle any future filename containing `pages` as a non-prefix
substring (e.g., `_blog/about-pages.mdx` → `/blog/about-`). No current
files trigger this; defensive hardening.

## Testing
Regenerated the sitemap locally and verified:
- [x] `grep -c "supabase.comdata" public/sitemap_www.xml` → `0` (was 4)
- [x] `<loc>https://supabase.com/terms</loc>` and
`<loc>https://supabase.com/enterprise-terms</loc>` still present
- [x] Every `<loc>` matches
`^<loc>https://supabase\.com(/[a-zA-Z0-9].*)?</loc>$` (no malformed URLs
of any kind)
- [x] Total loc count stable across both commits (regression-free for
the anchor change)

Local count is lower than prod (527 vs 906) because
`.next/server/pages/**` partner/expert/feature HTML globs only resolve
after a full build — runs correctly via `postbuild` on Vercel.

After deploy lands, resubmit `sitemap_www.xml` in Google Search Console
to force a re-crawl (otherwise daily-ish). Expect status to flip from "4
errors" to "Success" and Discovered pages: 906 → 902.

## Linear
- fixes GROWTH-837

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Chores**
* Improved sitemap generation to properly index specific content
sections (blog, case studies, customers, events, and alternatives) with
refined route path processing for better search engine discoverability.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45775)

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-11 15:14:36 +08:00
Pamela Chia 5a5099adba fix(docs): always emit BreadcrumbList item field (#45744)
## Summary

Eliminates the Google Search Console "Missing field 'item' (in
'itemListElement')" critical error on 230 `/docs/guides/*` pages. The
schema was emitting `ListItem`s without an `item` field for intermediate
category nodes that lack a URL in the docs nav. Per [Google's
spec](https://developers.google.com/search/docs/appearance/structured-data/breadcrumb),
`item` is required on every BreadcrumbList position except the last leaf
— so url-less items are filtered out instead.

Also fixes a smaller quality gap surfaced during preview verification:
the `auth` section root in `NavigationMenu.constants.ts` was missing a
`url`, so auth trails were dropping the "Auth" breadcrumb level (`Docs >
Guides > JSON Web Tokens (JWT) > Overview` instead of `Docs > Guides >
Auth > JSON Web Tokens (JWT) > Overview`). Every other section root
already has a `url`; auth was the lone outlier.


## Testing

Tested locally via vitest (`pnpm --filter docs exec vitest run
lib/json-ld.test.ts`):
- [x] All-urls chain: every `itemListElement` has string `item` and
`name`
- [x] Leaf-url-mismatch: leaf uses `pathname` even when the chain leaf
URL differs
- [x] All-url-less chain: returns `null`
- [x] Empty chain: returns `null`

Tested on the preview deploy against 7 representative GSC-flagged paths:
- [x] `/docs/guides/getting-started/ai-prompts` — 4 positions, 0 missing
- [x] `/docs/guides/getting-started/ai-skills` — 4 positions, 0 missing
- [x] `/docs/guides/auth/jwts` — 4 positions, 0 missing (after auth fix:
includes "Auth")
- [x] `/docs/guides/auth/social-login/auth-google` — 4 positions, 0
missing (after auth fix: includes "Auth")
- [x] `/docs/guides/database/postgres-js` — 4 positions, 0 missing
- [x] `/docs/guides/storage/quickstart` — 4 positions, 0 missing
- [x]
`/docs/guides/platform/migrating-within-supabase/dashboard-restore` — 5
positions, 0 missing

Post-merge:
- [ ] validator.schema.org against deployed URL: 0 errors
- [ ] GSC "Validate fix" on the breadcrumb issue (1-2 week re-crawl
window)

## Linear

- fixes GROWTH-835

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved breadcrumb validation to filter incomplete entries and avoid
broken documentation links.
* Restored root link for the Auth navigation section so the Auth menu
item now navigates to /guides/auth.

* **Tests**
* Added comprehensive tests covering breadcrumb generation and edge
cases.

* **Refactor**
* Streamlined breadcrumb JSON‑LD schema generation for clearer output
and maintainability.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45744)

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-11 12:57:22 +08:00
Pamela Chia dc0cc42d14 chore(www): stop tracking auto-generated rss.xml (#45745)
## Summary

`apps/www/public/rss.xml` is committed to the repo AND rewritten on
every `pnpm dev`, `pnpm build`, and `pnpm typecheck` run via
`content:build` -> `apps/www/scripts/generateStaticContent.mjs`. Anyone
working in `apps/www` (or running `pnpm typecheck` from the monorepo
root) sees a phantom `M apps/www/public/rss.xml` in `git status` every
session. Easy to accidentally commit, otherwise has to be repeatedly
stashed.

The sibling `changelog-rss.xml` (written by the same script) is already
gitignored. This aligns the main blog rss with the same treatment.

## Changes

- Add `/public/rss.xml` to `apps/www/.gitignore` (alongside the existing
changelog-rss entries; renamed the section header from "Changelog
generated feeds" to "Generated feeds" to reflect what it now covers).
- `git rm --cached apps/www/public/rss.xml` so git stops tracking the
file. The on-disk copy is preserved for local dev.

## Testing

No behavior change — the file is still generated by `content:build` and
served from `public/` at build time.

- [x] `git check-ignore -v apps/www/public/rss.xml` resolves to
`apps/www/.gitignore:36`
- [x] `apps/www/public/rss.xml` still on disk after `git rm --cached`
- [ ] On a fresh clone + `pnpm install + pnpm --filter www build`, the
file regenerates and is served at `/rss.xml` on the resulting build
(Vercel preview will confirm via deploy).

## Linear

- fixes GROWTH-836

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated repository configuration to properly manage auto-generated
files.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45745)

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-11 12:57:09 +08:00
Pamela Chia 5ce163fd69 feat(docs): add BreadcrumbList JSON-LD to guide pages (#45477)
## Summary

Emits `BreadcrumbList` JSON-LD on every `/docs/guides/*` page served by
`GuideTemplate`. Search engines and AI crawlers get an explicit
hierarchical signal for the docs site (the marketing site already
shipped JSON-LD via #45451). The chain prepends `Docs > Guides` to the
existing resolver output, so a page like `/docs/guides/auth/passwords`
produces a 5-level chain with the leaf URL set per Google's spec.

## Changes

- New `apps/docs/lib/breadcrumbs.ts`: pure pathname → chain resolver,
server-safe. Extracted from the existing client `useBreadcrumbs` hook so
the same logic runs in both contexts.
- New `apps/docs/lib/json-ld.ts`: `serializeJsonLd` +
`breadcrumbListSchema` mirroring `apps/www/lib/json-ld.ts`.
- `Breadcrumbs.tsx` (visual) now delegates to the shared resolver —
single source of truth for visual + SEO chains.
- `GuideTemplate` takes a required `pathname` prop and emits `<script
type="application/ld+json">` next to `<Breadcrumbs />`. Skipped when the
chain is empty (e.g., page not in nav menu). Middle items without URLs
(e.g., the "Auth" section root) omit `item`, matching the visual
breadcrumb.
- 8 explicit-prop callers updated; `[[...slug]]` callers already spread
`data` (which carries `pathname`).

## Scope

**Out of scope:**
- `/docs/reference/*` (SDK reference) — no breadcrumbs rendered today,
would need separate traversal over spec JSON.
- `/guides/troubleshooting/*` — uses its own template, not
`GuideTemplate`.
- `TechArticle` per-page schema — high maintenance for marginal value.

## Testing (Vercel preview)

```bash
curl -s https://<preview>/docs/guides/auth/passwords | grep -oE '<script type="application/ld\+json"[^>]*>[^<]+</script>'
```

Expect a script tag with the chain `Docs > Guides > Auth > Flows
(How-tos) > Password-based`, leaf URL
`https://supabase.com/docs/guides/auth/passwords`.

- [x] `/docs/guides/auth/passwords` — 5-item chain, leaf URL present
- [x] `/docs/guides/getting-started/features` — 4-item chain, all items
have URLs
- [x] `/docs/guides/getting-started/ai-prompts/<slug>` — special-case
chain (`Getting started > AI Tools > Prompts > <slug>`), leaf URL falls
back to pathname
- [x] `/docs/guides/database/database-advisors` (explicit-prop caller) —
chain renders
- [x] Visual breadcrumb on the same pages still renders correctly
- [ ] Validate output through [Google Rich Results
Test](https://search.google.com/test/rich-results) on a deployed preview
URL
- [x] `/docs/guides/troubleshooting/<slug>` — no JSON-LD emitted
(different template, intentional)

## Linear

- fixes GROWTH-820

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added JSON-LD breadcrumb markup to guide pages to improve
search/discovery.

* **Improvements**
* Centralized breadcrumb generation for consistent, accurate breadcrumbs
across guides.
* Multiple guide pages updated to ensure breadcrumbs and page context
display correctly.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-07 12:09:41 +08:00
Pamela Chia c26b64a033 feat(www): emit BreadcrumbList JSON-LD on marketing surfaces (#45478)
## Summary

- Adds `breadcrumbListSchema(items)` helper to `apps/www/lib/json-ld.ts`
and a hand-curated `apps/www/lib/breadcrumbs.ts` route map.
- Wires inline `<script type="application/ld+json">` BreadcrumbList
blocks into 18 marketing surfaces: blog (index + slug), customers (index
+ slug), events (index + slug), 5 product pages (database, auth,
storage, edge-functions, realtime), 3 modules (vector, cron, queues),
pricing, careers, company, features.
- Pages router callers wrap the script in `<Head>`; app router callers
place it directly in JSX. Dynamic surfaces append a leaf at render time
using the page's title (`frontmatter.title` for blog, `meta_title ??
title` for customers, `event.meta_title ?? event.title` for events).
- Modules sit at `Home > {Name}` since no `/modules` index page exists;
products sit at `Home > {Product}` (no shared products parent). Absolute
`https://supabase.com` URLs match the existing `CANONICAL_ORIGIN`
convention so anchors stay stable across Vercel previews.

Linear:
[GROWTH-822](https://linear.app/supabase/issue/GROWTH-822/add-breadcrumblist-json-ld-to-www-marketing-surfaces)
(sub-issue under
[GROWTH-724](https://linear.app/supabase/issue/GROWTH-724)).

> **Note on branch name:** the branch is
`pamela/growth-820-www-breadcrumb-jsonld`; the actual Linear issue is
GROWTH-822. The branch was named before the sub-issue was created.
Ignore the `820` in the branch.

Explicitly deferred (separate PRs / low SEO ROI): `/launch-week/*`,
`/solutions/*`, `/partners/*`, `/alternatives/*`, `/changelog`,
`/legal/dpa`, `/aws-reinvent-2025`, `/wrapped`, `/contribute/*`,
`/brand-assets`, `/ga`, `/ga-week`, `/state-of-startups*`, and the
homepage (Organization + WebSite already cover homepage entity signals;
single-item BreadcrumbList is ignored by Google).

## Test plan

- [x] On the Vercel preview, `curl -s https://<preview>/database | grep
'"BreadcrumbList"'` returns the script block with `Home > Database`.
- [x] `curl -s https://<preview>/blog/<recent-slug> | grep
'"BreadcrumbList"'` returns `Home > Blog > {post title}`.
- [x] `curl -s https://<preview>/customers/<slug> | grep
'"BreadcrumbList"'` returns `Home > Customer Stories > {customer
title}`.
- [x] `curl -s https://<preview>/events/<slug> | grep
'"BreadcrumbList"'` returns `Home > Events > {event title}`.
- [x] `curl -s https://<preview>/modules/vector | grep
'"BreadcrumbList"'` returns `Home > Vector`.
2026-05-05 23:57:53 +08:00
Pamela Chia e2480538ad feat(www): add JSON-LD structured data to homepage, products, and blog (#45451) 2026-05-01 23:55:32 +09:00
Pamela Chia 97583f0791 feat(www): include pricing FAQ in generated pricing.md (#45455) 2026-05-01 21:54:19 +09:00
Pamela Chia db0379f848 fix(www): normalize blog frontmatter dates to ISO 8601 (#45453) 2026-05-01 21:54:00 +09:00
Pamela Chia 5823986e72 chore(www): explicit AI crawler rules + homepage canonical URL (#45450) 2026-05-01 20:11:49 +09:00
Pamela Chia baabcb189c feat(www): serve blog, customers, events as .md for AI agents (#45403) 2026-05-01 14:57:48 +09:00
Pamela Chia dff4744805 fix(www): respect Accept q-values and 406 unsupported types (#45394) 2026-05-01 14:47:33 +09:00
Pamela Chia a98a4928b4 feat(www): rewrite to md for known llm user agents (#45328) 2026-04-29 02:41:24 +09:00
Pamela Chia 99447cdcbe chore: gitignore stale committed sitemap_www.xml and ui-library llms.txt (#45289)
## Why

Two auto-generated files have been tracked in git but the committed
copies are stale because every Vercel deploy overrides them in the build
artifact. Both have misled at least one investigation into thinking
production was out of date.

| File | Generator | Live (prod) | Committed (master) |
|---|---|---|---|
| `apps/www/public/sitemap_www.xml` | `pnpm postbuild` ->
`internals/generate-sitemap.mjs` | 704 URLs, `last-modified` matches
latest deploy | 604 URLs, last touched 2025-09-30 (`cb66b6b6`) |
| `apps/ui-library/public/llms.txt` | `pnpm build:llms` ->
`scripts/build-llms-txt.ts` (wired into `build`) | 57 entries, `Last
updated: 2026-04-27` | 56 entries, `Last updated: 2026-02-20` (last
touched 2026-03-17, `9bf981f3`) |

Production has been correct the whole time. The git copies just look
authoritative when they aren't, and they show up in audits and code
searches as if they were the live data.

The same gitignore pattern is already applied to
`apps/www/public/sitemap.xml` at `apps/www/.gitignore:29` — this PR
extends the pattern to two more files that should never have been
committed.

Discovered while investigating AI-bot crawl optimization for
`supabase.com/llms-full.txt`. An audit initially flagged "sitemap is
stale, missing 44 blog posts" — that turned out to be entirely about
reading the dead git file. Same kind of false signal would happen for
anyone inspecting the ui-library llms.txt locally.

## What changes

**`apps/www`:**
- Add `public/sitemap_www.xml` to `apps/www/.gitignore`.
- `git rm --cached apps/www/public/sitemap_www.xml`.
- Updated the gitignore comment to mention the regeneration path.

**`apps/ui-library`:**
- Add `public/llms.txt` to `apps/ui-library/.gitignore` (with a comment
pointing at the generator).
- `git rm --cached apps/ui-library/public/llms.txt`.

Both files remain on disk locally; their respective build steps
regenerate them as before.

## What does NOT change

- Production behavior for either file: postbuild / build:llms remain
authoritative.
- Generator code: `apps/www/internals/generate-sitemap.mjs` and
`apps/ui-library/scripts/build-llms-txt.ts` are untouched.
- robots.txt, sitemap index, docs sitemap: unchanged.
- `apps/learn/public/llms.txt`: left tracked. Its generator
(`apps/learn/scripts/build-llms-txt.ts`) is **not** wired into the
`build` script, so it's manually generated and committed — different
pattern, leave alone.

## Test plan

- [ ] After merge, trigger or wait for next prod deploy. Confirm
`https://supabase.com/sitemap_www.xml` still serves a fresh ~700-URL
response with current `last-modified` header.
- [ ] Confirm `https://supabase.com/ui/llms.txt` still serves a fresh
response with `Last updated:` matching the deploy date.
- [ ] Confirm `git ls-files apps/www/public/ apps/ui-library/public/` no
longer lists either file.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Improved handling of auto-generated sitemap files so they are no
longer tracked in the repo after builds.
* **Documentation**
* Removed the published auto-generated LLMs reference page from the UI
library to avoid shipping stale documentation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-28 16:42:41 +09:00
Pamela Chia d409836ca7 feat(www,docs): serve marketing pages as .md, advertise via link rel=alternate (#45277)
## Summary

Adds `/<page>.md` routes for 10 marketing/product pages (homepage, auth,
database, edge-functions, realtime, storage, vector, pricing,
modules/cron, modules/queues) so AI agents can fetch clean markdown
instead of parsing JS-rendered HTML. Also advertises the markdown
alternate via `<link rel="alternate" type="text/markdown">` on marketing
and docs pages so agents can discover it.

Pricing is generated dynamically via `generatePricingContent()` (single
source of truth with `/llms.txt` and `/llms-full.txt`); the other nine
slugs are bundled at build time from `content/md/*.md` into a
`MD_CONTENT` map.

Supersedes #44891 (rebased fresh off current master to avoid a 9-commit
replay over rename/rename conflicts created by #44897).

## Changes

- New `/api-v2/md/[...slug]` route handler returns the bundled markdown
(or dynamic pricing) with `Content-Type: text/markdown`,
`X-Content-Type-Options: nosniff`, and appropriate cache headers
- Middleware rewrites `/<slug>.md` and `Accept: text/markdown` to the
API route for the `MD_PAGES` allowlist; trailing-slash variants
(`/auth/`) are normalized so they resolve the same as `/auth`
- Build-time codegen `scripts/generateMdContent.mjs` scans `content/md/`
and emits `app/api-v2/md/content.generated.ts` exporting both
`MD_CONTENT` (Map) and `MD_PAGES` (Set, incl. dynamic `pricing`). Fails
the build on slug collision between `content/md/` and `DYNAMIC_SLUGS`.
Adding a new marketing `.md` is just dropping a file in `content/md/`
(also update `PRODUCT_OVERVIEW_LINKS` in `/llms.txt` since that list is
editorial).
- 8 permanent redirects `/llms/<product>.txt` → `/<product>.md` so
legacy URLs in caches and downstream `llms.txt` copies keep working
- `/llms.txt` product overview now references `.md` URLs (incl.
`modules/cron`, `modules/queues`); `/llms-full.txt` iterates
`MD_CONTENT.values()` (homepage first, then alphabetical) and appends
dynamic pricing
- `/llms/[slug]` route slimmed to proxy SDK reference files (`js.txt`,
`dart.txt`, etc.) since redirects handle product slugs and pricing;
pricing branch retained as fallback in case redirects are bypassed
- `apps/www/pages/_app.tsx` injects the alternate link conditionally
based on `MD_PAGES`; `/pricing` (app router) sets it via page metadata
- `apps/docs/app/page.tsx` (the `/docs` root) sets the text/markdown
alternate to `/llms-full.txt`; per-guide pages override with their
specific `.md` URL via `genGuideMeta` in `GuidesMdx.utils.tsx`. Other
docs pages (reference, troubleshooting) inherit nothing.
- `apps/www/.vercelignore`: replaces the prior `*.md`/`README.md` rules
with `*.md` + `!content/md/**/*.md` so Edge Function READMEs and future
scratch `.md` files aren't silently shipped to the build artifact
- Drops `apps/www/data/llms/*.txt` and the related
`outputFileTracingIncludes`
- Test coverage for the new middleware branches: `.md` suffix rewrite
(allowlisted vs. fall-through), `Accept: text/markdown` content
negotiation, trailing-slash normalization

## Testing (Vercel preview)

Local dev server smoke tests passing on `:3771` after each iteration.
Re-verified on the preview URL after the latest hardening commit:

- [x] `curl -I https://<preview>/llms/auth.txt` — expect `308 Permanent
Redirect` to `/auth.md`
- [x] `curl https://<preview>/auth.md | head -3` — expect `# Supabase
Auth`
- [x] `curl https://<preview>/pricing.md | head -3` — expect `# Supabase
Pricing` with current tier values
- [x] `curl https://<preview>/modules/cron.md | head -3` — expect `#
Supabase Cron`
- [x] `curl -H 'Accept: text/markdown' https://<preview>/ | head -3` —
expect `# Supabase` (homepage.md)
- [x] `curl https://<preview>/llms.txt` — Product Overview section lists
`.md` URLs and includes Cron + Queues
- [x] `curl https://<preview>/llms-full.txt | grep -E '^# Supabase
(Cron\|Queues\|Pricing)'` — Cron and Pricing each match once; Queues
matches twice (marketing module + existing docs guide)
- [x] View source on `/`, `/pricing`, `/database` — expect `<link
rel="alternate" type="text/markdown" href="/<slug>.md">`
- [x] View source on `/docs` — expect `<link rel="alternate"
type="text/markdown" href="/llms-full.txt">`
- [x] View source on a docs guide page (e.g., `/docs/guides/auth`) —
expect per-guide `.md` alternate; reference/troubleshooting pages should
NOT emit a markdown alternate
- [x] `curl -I https://<preview>/auth.md` — expect
`X-Content-Type-Options: nosniff`
- [x] `curl -I -L -H 'Accept: text/markdown' https://<preview>/auth/` —
should resolve to markdown content (trailing-slash normalization, with
Vercel's auto-redirect)

## Linear

- fixes GROWTH-760

## Follow-up (separate PR)

GROWTH-760 also asks about extending `.md` to blog/customers/events.
Different mechanism (path-prefix middleware, MDX read at request time
via `gray-matter`) so it deserves its own review. Will open a follow-up
PR after this lands.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Serve prebuilt and dynamic Markdown docs via new markdown endpoints
and routing; pages now advertise markdown alternates (including
pricing).
  * Added Cron and Queues module documentation pages.

* **Documentation**
  * Minor formatting tweaks to Realtime and Storage docs.

* **Chores**
* Added build-time Markdown content generation and adjusted
ignore/deploy rules for generated files.
* Added redirects from legacy text-based product URLs to new markdown
pages.

* **Tests**
* Expanded tests for markdown routing and content-negotiation behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-28 16:41:03 +09:00
Pamela Chia 2a9555d459 feat(docs,www): make supabase.com machine-readable for LLM agents (#44670)
## Summary

Makes supabase.com machine-readable for LLM agents and AI crawlers. Adds
a combined `llms-full.txt` (product overview + docs), curated product
overview `.txt` files, auto-generated pricing content, and updates the
`llms.txt` index to reference everything.

**Why:** supabase.com had `llms.txt` pointing to per-SDK doc files, but
no `llms-full.txt` (every competitor has one), no machine-readable
marketing content, and no pricing data agents could parse. Agents
evaluating Supabase got docs but no product overview or pricing, leading
to inaccurate comparisons.

## What's new

### `llms-full.txt` (auto-generated, combines www + docs)
- Product Overview section (~500 lines): homepage, all 6 products,
pricing
- Documentation section (~122K lines): guides, SDK references, CLI
reference
- 4.4MB total, regenerated on every docs deploy
- Reads product `.txt` files from `apps/www/public/llms/` at build time

### Product overview `.txt` files (`apps/www/public/llms/`)
- Curated summaries: homepage, database, auth, storage, edge functions,
realtime, vector
- `pricing.txt` is auto-generated from `packages/shared-data` (plans,
pricing, compute add-ons) via `generateLlmsPricing.mjs`, includes full
feature comparison matrix
- Other product files are hand-maintained (these pages change ~1x/year
per git history)
- Reminder comments added to all 7 marketing page source files

### `llms.txt` index (auto-generated)
- Two sections: Documentation (existing SDK/guide links) and Product
Overview (marketing page links)
- Links to `llms-full.txt` for bulk ingestion

### Rewrite changes
- Added `/llms-full.txt` rewrite to docs app
- Scoped `/llms/*.txt` wildcard to only match docs source slugs (guides,
js, dart, etc.), so marketing `.txt` files in `www/public/llms/` are
served directly

## Changes

- `apps/docs/scripts/llms.ts`: generate `llms-full.txt` combining www
product content + docs, update `llms.txt` index with marketing links
- `apps/www/lib/rewrites.js`: add `llms-full.txt` rewrite, scope docs
proxy to known slugs
- `apps/www/public/llms/*.txt`: 8 product overview files (7
hand-curated, 1 auto-generated)
- `apps/www/scripts/generateLlmsPricing.mjs`: build script generating
pricing.txt from shared-data; uses `getPlanValue()` guard in
`buildAddOnsSection` and `buildFeatureComparisonSection` to handle
missing plan keys defensively
- `apps/www/package.json`: add pricing generation to content:build
- `apps/www/pages/*.tsx`: reminder comments for LLM content updates

## Testing

Tested locally:
- [x] `pnpm run build:llms` generates combined `llms-full.txt` (4.4MB)
with Product Overview + Documentation sections
- [x] `llms.txt` index has Documentation + Product Overview sections
with `llms-full.txt` reference
- [x] `pricing.txt` auto-generated from shared-data with correct plan
tiers, compute add-ons, disk pricing, and feature comparison
- [x] Scoped rewrite regex matches docs slugs but not marketing slugs
- [x] Marketing `.txt` files served from `public/`

Post-deploy verification:
- [ ] `curl https://supabase.com/llms-full.txt` returns combined product
+ docs content
- [ ] `curl https://supabase.com/llms/database.txt` returns product
overview (not proxied to docs)
- [ ] `curl https://supabase.com/llms/guides.txt` still proxies to docs
app
- [ ] `curl https://supabase.com/llms/pricing.txt` returns
auto-generated pricing

## Maintenance

| Content | Auto-updates? | Trigger |
|---------|--------------|---------|
| `llms-full.txt` | Yes | Every docs deploy |
| `llms.txt` index | Yes | Every docs deploy |
| `pricing.txt` | Yes | Every www build (reads from shared-data) |
| Product `.txt` files (7) | No | ~1x/year, reminder comments in source
pages |

## Linear

- fixes GROWTH-758

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Added comprehensive AI-friendly product overviews (Auth, Database,
Edge Functions, Realtime, Storage, Vector) and a detailed pricing
document with plan comparisons, add-ons, disk tiers, and feature tables
* Added a consolidated "full" markdown output that combines curated
product overview content with per-source documentation

* **Chores**
* Build now generates the richer documentation outputs and pricing
automatically
* Improved routing so only scoped documentation .txt assets are proxied
while others are served directly
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-15 00:48:35 +09:00
Pamela Chia 2536593594 docs: CLI telemetry disclosure (#44662)
## Summary

The CLI now collects usage telemetry (supabase/cli#5019) with opt-out
via `supabase telemetry disable` or `SUPABASE_TELEMETRY_DISABLED=1`. The
self-hosting docs previously stated "no telemetry" without
distinguishing Docker from the CLI. This PR discloses CLI telemetry
across docs and regenerates the CLI reference spec to include the new
telemetry commands.

## Changes
- Update self-hosting telemetry section: Docker has no telemetry, CLI is
separate with opt-out instructions and link to full telemetry docs
- Regenerate `cli_v1_commands.yaml` from CLI v2.88.0, adding
consolidated `supabase telemetry` reference page
- Add Telemetry category to `common-cli-sections.json` for CLI reference
nav
- Add telemetry section to CLI getting-started guide with opt-out
commands and env vars

## Testing

Tested on Vercel preview:
- [x] `/docs/guides/self-hosting#telemetry` renders with CLI telemetry
note and link to full docs
- [x] `/docs/reference/cli/supabase-telemetry` renders single
consolidated reference page
- [x] `/docs/guides/local-development/cli/getting-started#telemetry`
shows opt-out section

## Linear
- fixes GROWTH-754
2026-04-08 22:06:56 +09:00
Pamela Chia bb66ba884c docs(billing-faq): add paused/deleted project usage note (#44570)
## Summary
- Add an Admonition note to the Fair Use Policy section clarifying that
pausing or deleting a project does not remove accumulated usage from the
current billing cycle
- Addresses a common source of confusion identified in #team-support
thread (2026-04-02)

## Test plan
- [ ] Preview the MDX locally to confirm Admonition renders correctly

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Updated billing FAQ to clarify that pausing or deleting a project
stops new usage from accruing but does not remove usage already incurred
in the current billing cycle; quota-based usage remains counted until
the billing period resets.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-06 17:42:22 +09:00
Pamela Chia 4c10f65de7 feat(tracking): add compute_badge_upgrade_clicked event (#44560)
## Summary

The `compute_badge_upgrade_clicked` event had zero fires since Apr 1
because the PostHog tracking call was never wired up on the "Upgrade
compute" button in the compute badge hover card. This adds the missing
`useTrack` call using the existing event definition in
`telemetry-constants.ts`.

## Changes
- Add `useTrack` hook and `onClick` handler to the upgrade button in
`ComputeBadgeWrapper.tsx`
- Fires `compute_badge_upgrade_clicked` with `computeSize`, `planId`,
and `upgradeType` properties
- Preserves existing `asChild` + `<Link>` navigation pattern

## Testing

Tested on Vercel preview:
- [x] Hover compute badge on a project with non-max compute, click
"Upgrade compute", verify `compute_badge_upgrade_clicked` event appears
in PostHog live events with correct properties
- [x] Verify navigation to compute settings page still works
(client-side, no full reload)
- [x] Test with paid-plan nano project: `upgradeType` should be
`free_micro_upgrade`
- [x] Test with paid-plan non-nano project: `upgradeType` should be
`compute_upgrade`

## Linear
- fixes GROWTH-751

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Added analytics tracking for compute upgrade button clicks to record
upgrade type (free micro vs. paid), selected compute size (with
fallback), and plan identifier. This ensures upgrade interactions are
captured for product insights without changing visible UI behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-06 17:11:48 +09:00
Pamela Chia 8364ffb5e0 chore(studio): remove privacy policy update notification banner (#44380)
## Summary

Remove the privacy policy update notification banner that was added for
the March 2026 policy update. The effective date has passed and users
have had sufficient notice.

## Changes

- Remove `PrivacyUpdateBanner` component from `AnalyticsSettings.tsx`
and its unused imports
- Remove banner usage from the organizations list page and org projects
page
- Remove `PRIVACY_NOTICE_ACKNOWLEDGED` localStorage key

## Testing

No setup needed - this is a removal of UI elements.

Tested on Vercel preview:
- [x] Organizations page loads without the banner
- [x] Org projects page loads without the banner
- [ ] No console errors on either page
- [x] Analytics settings page still renders correctly

## Linear

- fixes GROWTH-692
2026-03-31 19:54:04 +08:00
Pamela ChiaandAlaister Young edacf2413d chore(studio): ship connect section, remove getting started and experiment plumbing (#44329)
## Summary

The `connectSection` A/B experiment concluded as a true null (no effect
on activation or any downstream metric after 13 days at 50/50, ~153K
mature orgs). Saxon decided to ship the Connect section as the permanent
experience. This PR removes the Getting Started control variant, the old
Connect modal, all experiment flag gating, and related telemetry types.

## Changes

- Delete `GettingStarted/` directory (5 files: section component, types,
utils, progress hook)
- Delete old `Connect.tsx` dialog modal (replaced by ConnectSheet)
- Remove `connectSection` PostHog flag reads from `Home.tsx` and
`LayoutHeader.tsx`
- Remove `getSectionVisibility()` experiment logic and
`ConnectSectionVariant` type
- Remove `getting-started` from `DEFAULT_SECTION_ORDER`
- Always render `<ConnectSheet />` in header (no more conditional with
old `<Connect />` modal)
- Remove `variant` prop from `ConnectSection` component
- Remove 4 getting-started telemetry event interfaces from
`telemetry-constants.ts`
- Update `mergeSectionOrder` tests to reflect new section order

## Testing

Tested on Vercel preview:
- [x] Project homepage shows Connect section for new projects (< 10 days
old)
- [x] Connect section hidden for mature projects (> 10 days old)
- [x] Header Connect button opens ConnectSheet (not old modal)
- [x] Connect tiles open ConnectSheet with correct tab
- [x] Section drag-and-drop still works without getting-started in the
order
- [x] Existing users with `getting-started` in localStorage order don't
break (mergeSectionOrder strips it)

## Linear

- fixes GROWTH-730

---------

Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
2026-03-30 20:51:09 +08:00
Pamela Chia e01fa312fb feat(studio): add connection_string_copied tracking to ConnectSheet (#44327) 2026-03-30 16:32:18 +08:00
Pamela Chia e09a663292 fix(billing): clarify service restriction reset delay after billing cycle (#44122)
## Summary

Customers were upgrading to Pro to clear service restrictions, unaware
that restrictions aren't lifted immediately when the billing cycle
resets — there's a delay. This adds a clarifying note to both the docs
FAQ and the Studio restricted alert so users know what to expect.

Note: upgrading your plan or disabling spend cap *does* lift
restrictions immediately (unchanged). The delay only applies to the
billing-cycle-reset path for usage-limit violations.

## Changes

- `billing-faq.mdx`: Removes the implication that restrictions clear the
moment the billing period resets; adds "Note that there may be a short
delay after your billing period resets before restrictions are fully
lifted"
- `Restriction.tsx`: Updates the restricted alert to make clear that
upgrading lifts restrictions immediately, while the billing-reset path
may have a delay

## Testing

To test the Studio banner, find or create an org with
`restriction_status === 'restricted'`, or temporarily hardcode
`shownAlert = 'restricted'` in `Restriction.tsx` to preview:

- [x] Restricted alert reads clearly and distinguishes immediate
(upgrade) vs delayed (billing reset) paths
- [x] Docs FAQ answer under "How can I remove restrictions" reads
correctly with the new caveat

## Linear
- fixes GROWTH-704
2026-03-24 18:08:29 +08:00
Pamela ChiaandCopilot Autofix powered by AI 882dff3bbb chore: add github copilot code review instructions (#43901)
## Summary

Adds `.github/copilot-instructions.md` to configure GitHub Copilot Code
Review with telemetry and testing guidelines. Replaces CodeRabbit as our
automated PR reviewer. Copilot will now advisory-comment when PRs touch
growth-oriented components (onboarding, upgrade CTAs, A/B experiments)
without telemetry tracking, or when Studio changes lack appropriate test
coverage.

## Changes

- Add `.github/copilot-instructions.md` with:
- Telemetry review rules: event naming (`[object]_[verb]`), property
conventions, `useTrack` enforcement, missing tracking suggestions for
growth surfaces
- Testing review rules: logic extraction to `.utils.ts`, test type
decision tree, file naming conventions
  - Repo context and references to full Claude skill docs

## Testing

- [x] Verified `.github/copilot-instructions.md` is the correct path for
Copilot Code Review
- [x] Rules are condensed from existing Claude skills
(`telemetry-standards`, `studio-testing`) — verified alignment

## Linear

- fixes GROWTH-698

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-18 18:15:29 +08:00
Pamela Chia 4a4fe253ce feat(www,studio): privacy policy amendments — advertising, cookie disclosure, banner (#43681) 2026-03-16 21:49:02 +09:00
Pamela Chia fe5b22d314 fix(studio): fall back to legacy connect dialog when connectSection flag is unresolved (#43733)
## Summary

When the `connectSection` PostHog flag is unresolved (`undefined`) —
e.g. during loading or if PostHog is unreachable — the header Connect
button renders nothing (`null`), making it a no-op. This changes the
fallback to render the legacy `<Connect />` dialog instead, so the
button always works.

## Changes

- Remove `isFlagResolved` gate in `LayoutHeader` — render `<Connect />`
(legacy) whenever `connectSection !== 'connect'`, including when
`undefined`

## Testing

Tested on Vercel preview:
- [x] Flag returns `'connect'` — ConnectSheet opens
- [x] Flag returns `'getting-started'` or `false` — legacy Connect
dialog opens
- [x] Flag is `undefined` (unresolved) — legacy Connect dialog opens
instead of nothing
2026-03-13 04:28:41 +00:00
Pamela ChiaandAli Waseem 578a73f966 feat(studio): connectSection experiment — replace Getting Started with Connect section (#43629)
## Summary

Re-ports PR #43119 against the current `ProjectHome` codebase (the
original PR targeted `HomeNew/` which was removed during the `homeNew`
graduation in #43437). Also unifies the `connectSheet` and
`connectSection` feature flags into a single `connectSection` flag — the
`connectSheet` flag is removed entirely, so both the ConnectSheet
(header) and ConnectSection (homepage row) are controlled by one
experiment.

- Adds `connectSection` PostHog experiment flag that controls two
things:
1. Swaps the Getting Started section for a Connect section on the
project homepage for new projects (< 10 days old)
2. Swaps the legacy Connect dialog for the new ConnectSheet panel in the
header
- **Control** (`getting-started`): existing Getting Started section +
legacy Connect dialog in header
- **Treatment** (`connect`): new 4-tile Connect section + ConnectSheet
in header
- `undefined` (loading): neither section renders, avoiding flash
- Tiles filtered by the same `useIsFeatureEnabled` flags as ConnectSheet
(`show_app_frameworks`, `show_mobile_frameworks`, `show_orms`)
- Connect tile clicks tracked via `home_connect_action_clicked`; section
render tracked via `home_connect_section_exposed`; sheet opens tracked
via `connect_sheet_opened` with source attribution (`header_button` or
`connect_section`)

## Changes

- `packages/common/telemetry-constants.ts` — new
`home_connect_section_exposed`, `home_connect_action_clicked`, and
`connect_sheet_opened` event types
- `ConnectSheet/ConnectSheet.tsx` — read `connectTab` query param and
sync to active mode on open; `handleModeChange` keeps param in sync on
tab switch; fire `connect_sheet_opened` event with source attribution on
open
- `ConnectButton/ConnectButton.tsx` — set `connectSource=header_button`
query param on click
- `ProjectHome/ConnectSection.tsx` — new component (4-tile connect
card); set `connectSource=connect_section` on tile click
- `ProjectHome/Home.tsx` — experiment flag wiring
- `LayoutHeader/LayoutHeader.tsx` — read `connectSection` flag instead
of `connectSheet` to toggle ConnectSheet vs legacy Connect dialog

## Test plan

To test on the Vercel preview, set `connectSection=connect` to 100% in
PostHog (or override via cookie `ph_override_connectSection=connect`).
No separate `connectSheet` flag is needed — `connectSection` controls
both features.
- [x] Treatment (`connect` variant) — "Get connected" section renders on
new project, Getting Started hidden
- [x] Control (`getting-started`) — Getting Started renders, Connect
section hidden
- [x] Mature project (> 10 days) — neither section regardless of flag
- [x] Clicking each tile opens ConnectSheet on the correct tab
(Framework / Direct / ORM / MCP)
- [x] Switching tabs inside sheet updates `connectTab` URL param
- [x] Closing sheet clears `connectTab` param
- [x] Direct URL deep-link (`?showConnect=true&connectTab=orm`) opens
sheet on correct tab
- [x] Dark mode — background gradient renders correctly
- [x] Light mode — background switches to light gradient
- [x] Responsive layout — 4 cols (xl), 2×2 (md), stacked (mobile)
- [x] Telemetry — `home_connect_section_exposed` fires once on load;
`home_connect_action_clicked` fires with correct `mode`
- [x] Treatment — header Connect button opens ConnectSheet (not legacy
Connect dialog)
- [x] Control — header Connect button opens legacy Connect dialog (not
ConnectSheet)
- [x] Telemetry — clicking a ConnectSection tile fires
`connect_sheet_opened` with `source: 'connect_section'`
- [x] Telemetry — clicking header Connect button fires
`connect_sheet_opened` with `source: 'header_button'`

---------

Co-authored-by: Ali Waseem <waseema393@gmail.com>
2026-03-13 11:50:07 +09:00
Pamela Chia 01c178e159 chore(studio): graduate homeNew experiment (#43437)
## Summary

The `homeNew` PostHog experiment has concluded. This PR graduates it by
making the new homepage (`ProjectHome`, formerly `HomeV2`) the permanent
default for all users, and removes all dead code from the old
experiment.

## Changes

- Remove `homeNew` PostHog feature flag checks and `home_new` experiment
exposure tracking from 3 files
- Rename `HomeNew/` → `ProjectHome/` directory and `HomeV2` →
`ProjectHome` export
- Delete old `Home/Home.tsx` component (shared components like
`ProjectList/` are kept — still used by org pages)
- Delete `pages/project/[ref]/building.tsx` and add a server-side
redirect from `/project/:ref/building` → `/project/:ref` to prevent 404s
during rollout (old cached JS bundles may still route to `/building`)
- Simplify `ContentWrapper` building-state logic in `ProjectLayout` —
always redirect building projects to home, always suppress building
interstitial on home page
- Always route to `/project/{ref}` after project creation (remove
`/building` path)
- Update all Observability imports from `HomeNew` → `ProjectHome`

## Self-hosted behavior change

Self-hosted Studio previously showed the old `Home` component (client
libraries + example projects) since PostHog flags don't load. This PR
changes self-hosted to show `ProjectHome` (TopSection with service
status + instance diagram, advisor, custom reports). All sections query
backend APIs that exist on self-hosted. E2E tests pass against the
self-hosted build.

## Testing

- [x] `pnpm turbo run build --filter=studio` passes
- [x] No remaining references to `homeNew`, `home_new`, or `HomeNew` in
codebase
- [x] No broken imports to deleted files
- [x] Self-hosted E2E tests pass (145 passed, 1 flaky, 4 skipped)
- [x] `/building` redirect added to both platform and self-hosted config
blocks

**Quick test:**
1. Navigate to any project homepage — should render the ProjectHome
component
2. Create a new project — should redirect to `/project/{ref}` (not
`/building`)
3. Visit a project in `COMING_UP` state on a non-home route — should
redirect to home
4. Visit `/project/{ref}/building` directly — should 302 redirect to
`/project/{ref}`

## Linear

- fixes GROWTH-671
2026-03-10 17:03:58 +09:00
Pamela Chia 4a9455cb2f chore(studio): update downgrade survey to use 'what made you' framing (#43435)
## Summary

- Updates exit survey question wording from "why" framing to "what made
you" framing across both downgrade and project deletion flows
- Based on [Jason Cohen's
research](https://www.lennysnewsletter.com/p/why-your-product-stopped-growing)
showing this reframing roughly doubles response rates and improves
response quality by prompting users to recall a specific trigger event

### Changes

| Location | Before | After |
|---|---|---|
| ExitSurveyModal (downgrade) | "Share with us why you're downgrading
your plan." | "What made you decide to downgrade your plan?" |
| DeleteProjectModal (delete) | "Help us improve by sharing why you're
deleting your project." | "What made you decide to delete your project?"
|

### No downstream impact
- `CANCELLATION_REASONS` chip values unchanged
- API payload fields (`reasons`, `additionalFeedback`, `exitAction`)
unaffected
- No PostHog event names or properties tied to question wording

Closes GROWTH-657

## Test plan

- [ ] Trigger downgrade flow (paid plan → Free) and verify new wording
appears
- [ ] Trigger project deletion on a paid plan and verify new wording
appears
- [ ] Confirm survey submission still works end-to-end
2026-03-05 08:35:14 +00:00
Pamela Chia 5880966b15 chore: add telemetry standards skill for CodeRabbit (#43436)
## Summary

- Adds a combined telemetry standards skill
(`.claude/skills/telemetry-standards/SKILL.md`) that covers PostHog
event naming conventions, property standards, review rules, and
implementation guide
- Intended to be imported as CodeRabbit learnings after merge so
CodeRabbit can flag missing/incorrect tracking in PRs
- Consolidates standards from existing `review-telemetry` and
`implement-tracking` Claude commands into a single source of truth

## Post-merge steps

### 1. Import as CodeRabbit learnings (one-time)

Comment on any PR in the repo:
```
@coderabbitai add a learning using .claude/skills/telemetry-standards/SKILL.md
```

This teaches CodeRabbit the telemetry standards. It will then:
- Flag naming/property violations when `telemetry-constants.ts` is
changed
- Suggest adding `useTrack()` tracking when PRs add user-facing
interactions without it
- Propose event names following `[object]_[verb]` convention

### 2. Add path instructions in CodeRabbit web UI (optional,
recommended)

Go to CodeRabbit settings > Review > Path Instructions and add:

- **Path:** `packages/common/telemetry-constants.ts`
- **Instructions:** "Strictly enforce event naming: [object]_[verb] in
snake_case. Only approved verbs: opened, clicked, submitted, created,
removed, updated, retrieved, intended, evaluated, added. Properties must
be camelCase and self-explanatory. Flag any usage of
useSendEventMutation."

### 3. Remove old Claude commands (after verifying skill works)

Delete `.claude/commands/review-telemetry.md` and
`.claude/commands/implement-tracking.md` — this skill replaces both.

Closes GROWTH-661
2026-03-05 17:08:26 +09:00
Pamela Chia 3411387312 feat(telemetry): track log explorer query execution (#42714) 2026-02-12 17:26:16 +08:00
Pamela Chia ef84dddc22 feat(telemetry): track home page and getting started exposure (#42247)
## Summary

Adds exposure tracking for the HomeV2 experiment and the Getting Started
section. These events help measure user engagement with the new home
page experience and track how many users see the Getting Started
onboarding flow.

Resolves
[GROWTH-602](https://linear.app/supabase/issue/GROWTH-602/track-home-page-and-getting-started-exposure-events)

## Changes

- Add `home_new_experiment_exposed` event that fires when users see the
HomeV2 experiment (captures which variant they're assigned)
- Add `home_getting_started_section_exposed` event that fires when the
Getting Started section is displayed (captures current workflow state)
- Define corresponding TypeScript interfaces in telemetry-constants.ts
- Both events use `useRef` to ensure they only fire once per component
mount

## Testing

Tested locally and on staging that events fire to posthog.

**Quick test:**
1. Navigate to a project home page with the `homeNew` flag enabled
2. Verify `home_new_experiment_exposed` event fires in PostHog with the
correct variant
3. For projects < 10 days old, verify
`home_getting_started_section_exposed` fires when the section is visible
4. Confirm events don't fire for dismissed sections or mature projects

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added exposure tracking for home experiment variants and the Getting
Started section to surface which experience users see.

* **Chores**
* Standardized and consolidated analytics calls to improve reliability
of interaction tracking.
* Enhanced event payloads to capture workflow choices, step
interactions, and dismissals for better product insights.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-03 08:27:04 +00:00
Pamela Chia b6d8f2ae2d chore: remove tableQuickstart and realtimeButtonVariant experiments (#42388)
## Summary

Removes two concluded A/B experiments that didn't produce positive
results:
- **tableQuickstart**: Tested AI-powered table generation, template
selection, and assistant integration for new table creation
- **realtimeButtonVariant**: Tested hiding the realtime button or
replacing it with a triggers button

## Changes

- Delete `TableQuickstart/` folder with AI widget, templates widget, and
generation hooks
- Delete `useRealtimeExperiment` hook and remove variant-conditional
logic
- Delete `/api/ai/table-quickstart/generate-schemas` endpoint
- Remove telemetry event definitions for both experiments
- Remove local storage exposure tracking key
- Remove API endpoint from proxy whitelist
- Clean up eslint baseline references

## Testing

- [x] Tested locally - Table Editor renders correctly without experiment
widgets
- [x] TypeScript compiles without errors
- [x] No remaining references to removed experiment code

**Quick test:**
1. Navigate to Table Editor → New Tab shows only "Create a table" card
(no AI/Templates/Assistant variants)
2. Open table create panel → Realtime checkbox shows unconditionally
when realtime is enabled

## Linear

fixes GROWTH-609

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Removed Features**
* AI-powered table generation and quickstart assistance (templates, AI
widget, generation hook, templates data, and related utilities)
  * Quickstart templates widget and predefined table templates
  * Database triggers management interface
  * Realtime experiment gating and related experiment variants

* **API & Storage**
  * Hosted AI quickstart API endpoint removed
  * Local storage key for quickstart exposure tracking removed

* **Telemetry**
  * Quickstart- and realtime-experiment telemetry events removed

* **UI Changes**
  * Simplified realtime toggle control in the table editor
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-03 11:55:10 +08:00
Pamela Chia 30093962d4 feat(telemetry): dedupe experiment exposure events by PostHog session ID (#42386)
## Summary

Adds session-based deduplication for experiment exposure events.
Previously, exposure events used `useRef` which reset on page refresh,
causing duplicate events. Now events dedupe by PostHog session ID using
`sessionStorage`, firing once per session even across page refreshes.

## Changes

- Add `captureExperimentExposure()` method to `PostHogClient` with
session-based dedupe
- Add `getSessionId()` method to retrieve PostHog session ID
- Create `useTrackExperimentExposure` hook for clean usage
- Migrate existing experiments to new hook:
  - RLS option experiment (`/new/[slug]`)
  - Realtime button experiment
  - Table create generate policies experiment

## How it works

1. Event triggered → if PostHog not ready, queue to `pendingExposures[]`
2. PostHog `loaded` → flush queue through `fireExposureIfNew()`
3. `fireExposureIfNew()` checks
`sessionStorage['ph_exposed:{experimentId}']`
4. If value matches current session ID → skip (already fired)
5. If differs or missing → fire event, store session ID

## Testing

- [x] Tested locally and on preview - verified on project creation page
- [x] PostHog events sent successfully (200 responses)
- [x] Session deduplication working (no duplicate events on page refresh
or org switch)
- [x] Lint passes with 0 errors

**Quick test:**
1. Go to `/new/<org-slug>` project creation page
2. Check `sessionStorage` for `ph_exposed:project_creation_rls_option`
key
3. Refresh page - event should not fire again (same session ID)

## Linear

Resolves GROWTH-608

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* More reliable experiment exposure reporting with session-aware
deduplication, queued delivery, and consent respect to ensure events are
recorded when available.
* **Bug Fixes**
* Prevents duplicate exposure events within a session and avoids firing
exposures without a valid session.
* **Refactor**
* Consolidated disparate exposure-tracking logic into a unified
hook-based approach, simplifying tracking across the app.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-02 22:01:34 +08:00
Pamela Chia f27a2d3028 feat(telemetry): track security settings on project creation (#42150)
* feat(telemetry): add security settings to project creation event

Add dataApiEnabled, useApiSchema, and useOrioleDb properties to
project_creation_simple_version_submitted event to track user
security and configuration choices during project creation.

* feat(studio): track security settings on project creation

Include dataApiEnabled, useApiSchema, and useOrioleDb in the
project_creation_simple_version_submitted telemetry event to capture
user's security and configuration choices.

* feat(telemetry): add security settings to confirm modal event

Include dataApiEnabled, useApiSchema, and useOrioleDb in the
project_creation_simple_version_confirm_modal_opened event for
consistency with the submitted event.

* refactor(telemetry): remove extra properties from confirm modal event

Remove dataApiEnabled, useApiSchema, and useOrioleDb from
project_creation_simple_version_confirm_modal_opened as these
are not needed for this event.
2026-01-26 13:49:45 +08:00
Pamela Chia faa2588392 chore(studio): remove homeNew feature flag helper (#41984)
PostHog migration from boolean to string variants is complete.
The helper was only useful during migration - now it's just
`value === 'new-home'` which is trivial to inline.

- Delete apps/studio/lib/featureFlags/ folder
- Inline flag check at 3 call sites
2026-01-21 12:32:02 +07:00
Pamela Chia 0d4fc54fb3 docs(studio): clarify free project limit message (#42007)
docs: clarify free project limit across organizations
2026-01-20 16:45:38 +07:00
Pamela Chia 90fbf041ce feat(studio): add cached egress quota violation label (#42000)
feat(usage): add cached egress quota violation label
2026-01-20 14:16:45 +07:00
Pamela Chia 2346baf51c chore(telemetry): standardize event naming conventions (#41786)
* docs(telemetry-constants): add naming conventions and review note

* refactor(telemetry): rename event interfaces to include Event suffix

* refactor(telemetry): rename branch merge success event to completed

* refactor(telemetry): rename cron job deleted event to removed

* refactor(telemetry): rename events to submitted/completed

* docs: add previous event names to telemetry comments
2026-01-08 17:11:48 +07:00
Pamela Chia 3336b22451 chore(flags): migrate homeNew flag from configcat to posthog (#41101)
Switch homeNew flag to usePHFlag.
2026-01-05 15:22:06 +07:00
Pamela Chia 9f79037a8b feat(telemetry): add tracking for request upgrade modal (#41049) 2025-12-05 07:25:21 +00:00