fix(studio): drive compute card cores/memory from infra_compute_size (#45334)

## Summary

Fixes
[FE-3095](https://linear.app/supabase/issue/FE-3095/compute-size-hover-card-shows-badge-and-cpumemory-from-out-of-sync).

The compute size hover card on the project home dashboard was sourcing
its badge and its CPU/memory rows from two different cached responses,
which can disagree:

| Field shown | Previous source |
|---|---|
| Badge ("XLARGE") | `project.infra_compute_size` (project-detail query)
|
| Cores / memory | `selected_addons[compute_instance].variant.meta`
(project-addons query) |

A customer reported seeing an **XLARGE** badge next to **2-core ARM
(Shared) / 1 GB** — the micro-tier specs — and asked whether their
upgrade had actually been applied. The upgrade was applied; only the
rendered card was contradictory.

## Fix

Source both the badge and the CPU/memory rows from the same logical
fact: look up the variant in `available_addons` whose identifier matches
`ci_${infra_compute_size}` and read its `meta`. `available_addons` is
essentially a static catalog of variant specs, so once it's loaded the
card cannot show specs that disagree with the badge.

This also collapses the special-cased `INSTANCE_MICRO_SPECS` fallback
into the existing `getAvailableComputeOptions` helper (which already
provides micro/nano fallbacks). The nano UX text ("Shared / Up to 0.5
GB") is preserved by switching that JSX branch to key on `computeSize
=== 'nano'`.

## Out of scope

- `useProjectAddonUpdateMutation` does not invalidate
`projectKeys.detail`. That's hygiene worth doing later, but
project-detail has a 30s `staleTime` and the resize already drives 5s
polling via the `RESIZING` status path, so the badge refreshes naturally
and this fix doesn't depend on it.

## Test plan

- [ ] Hover the compute badge on a project at each compute size (nano,
micro, small, ..., 16xlarge) and confirm CPU and memory rows match the
badge.
- [ ] Resize a project from micro → large; on completion, confirm the
hover card shows large specs (no transient micro values).
- [ ] Open the dashboard for a free-tier project on micro that has no
`compute_instance` entry in `selected_addons` and confirm the card still
shows micro specs (i.e. `getAvailableComputeOptions` micro fallback is
engaged).
- [ ] Confirm the "Unlock more compute" CTA still appears for
non-highest sizes and disappears at the highest size.
This commit is contained in:
Ali Waseem
2026-04-28 11:55:19 -06:00
committed by GitHub
parent a98a4928b4
commit 6fe0ad442b
@@ -4,14 +4,12 @@ import { Button, cn, HoverCard, HoverCardContent, HoverCardTrigger, Separator }
import { ComputeBadge } from 'ui-patterns/ComputeBadge'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { getAddons } from '@/components/interfaces/Billing/Subscription/Subscription.utils'
import { getAvailableComputeOptions } from '@/components/interfaces/DiskManagement/DiskManagement.utils'
import { ProjectDetail } from '@/data/projects/project-detail-query'
import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-query'
import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query'
import { ProjectAddonVariantMeta } from '@/data/subscriptions/types'
import { ResourceWarning } from '@/data/usage/resource-warnings-query'
import { getCloudProviderArchitecture } from '@/lib/cloudprovider-utils'
import { INSTANCE_MICRO_SPECS } from '@/lib/constants'
import { useTrack } from '@/lib/telemetry/track'
export const ChevronsUpAnimated = () => (
@@ -76,23 +74,14 @@ export const ComputeBadgeWrapper = ({
{ projectRef },
{ enabled: open }
)
const selectedAddons = addons?.selected_addons ?? []
const { computeInstance } = getAddons(selectedAddons)
const computeInstanceMeta = computeInstance?.variant?.meta
const meta = (
computeInstanceMeta === undefined && computeSize === 'micro'
? INSTANCE_MICRO_SPECS
: computeInstanceMeta
) as ProjectAddonVariantMeta
const availableCompute = addons?.available_addons.find(
(addon) => addon.name === 'Compute Instance'
)?.variants
const highestComputeAvailable = availableCompute?.[availableCompute.length - 1].identifier
// Derive cores/memory from the same source as the badge (infra_compute_size) by looking up
// the matching variant in available_addons. Sourcing from selected_addons can drift out of
// sync with infra_compute_size and produce a card that contradicts its own badge.
const computeOptions = getAvailableComputeOptions(addons?.available_addons ?? [], cloudProvider)
const meta = computeOptions.find((variant) => variant.identifier === `ci_${computeSize}`)?.meta
const highestComputeAvailable = computeOptions[computeOptions.length - 1]?.identifier
const isHighestCompute = computeSize === highestComputeAvailable?.replace('ci_', '')
const { data, isPending: isLoadingSubscriptions } = useOrgSubscriptionQuery(
@@ -161,7 +150,12 @@ export const ComputeBadgeWrapper = ({
) : (
<>
<div className="flex flex-col gap-1">
{meta !== undefined ? (
{computeSize === 'nano' ? (
<>
<Row label="CPU" stat="Shared" />
<Row label="Memory" stat="Up to 0.5 GB" />
</>
) : meta !== undefined ? (
<>
<Row
label="CPU"
@@ -169,13 +163,7 @@ export const ComputeBadgeWrapper = ({
/>
<Row label="Memory" stat={`${meta.memory_gb ?? '-'} GB`} />
</>
) : (
<>
{/* meta is only undefined for nano sized compute */}
<Row label="CPU" stat="Shared" />
<Row label="Memory" stat="Up to 0.5 GB" />
</>
)}
) : null}
</div>
</>
)}