A gate answers one question: *may this render right now, and if not, what does
the user see instead?*

There are four, they ask genuinely different questions, and picking the wrong
one is how a screen ends up blocked for a reason the user cannot act on.

| Gate | Question | Blocked by |
|---|---|---|
| `ConnectAccessGate` | May this workspace use the application at all? | Suspended, no plan, onboarding pending, payment overdue |
| `ConnectSubscriptionGate` | Is there a live subscription? | No subscription, or a cancelled one |
| `ConnectFeatureGate` | Does their plan include this feature? | Feature not in the plan's `features` |
| `ConnectWorkspaceTypeGate` | Is this the right kind of workspace? | Individual account on a business-only screen |

## The access gate

The broad one, and the only one you get without asking — `AppShell`
wraps its children in it.

It covers four unrelated failures that all mean "not right now", and it renders
a different, actionable screen for each: a suspended workspace gets a support
route, an overdue payment gets a link to billing, pending onboarding gets a
link to finish it. That last detail is the reason to use the gate rather than
your own `if`: **a blocked screen has to tell the user what to do**, and four
branches of that are four chances to write a dead end.

```tsx
import { ConnectAccessGate } from "@cosmoops/connect-sdk/client";

<ConnectAccessGate>
  <Dashboard />
</ConnectAccessGate>
```

Every state it can be in, on one control — including the two that are easy to
forget you have never looked at, the loading frame and the signed-out one:

<Preview name="access-gate">

```tsx
import { ConnectAccessGate } from "@cosmoops/connect-sdk/client";

<ConnectAccessGate>
  <Dashboard />
</ConnectAccessGate>
```

</Preview>

The advisory half is separable. `ConnectReadinessBanner` says what is wrong
without blocking anything, which is what a payment retrying inside its grace
period deserves — the workspace is still a customer, and the screen still
works:

<Preview name="readiness-banner">

```tsx
import { ConnectReadinessBanner } from "@cosmoops/connect-sdk/client";

<ConnectReadinessBanner />
```

</Preview>

### Exempting a route

Some routes must render *while* blocked — the billing page most obviously,
since that is where the user goes to unblock themselves.

Declare it on the nav item, next to the route it applies to:

```tsx title="app/layout.tsx"
const NAV = [
  { label: "Billing", href: "/billing", icon: <CreditCard className="size-5" />, gateExempt: true },
];
```

For a route that is in neither nav — a payment callback, say — use
`gateExemptRoutes`, which is prefix-matched the same way:

```tsx
<AppShell nav={NAV} gateExemptRoutes={["/checkout/callback"]}>
  {children}
</AppShell>
```

<Callout type="warn" title="Exempt the route that fixes the problem">
  A payment-overdue gate over your billing page is a closed loop: the user
  cannot pay because they have not paid. Whatever route resolves each blocker
  has to be exempt.
</Callout>

### Reading the verdict yourself

When you want the decision but not the default screen:

```tsx
import { useConnectAccessGate } from "@cosmoops/connect-sdk/client";

const { blocked, reason } = useConnectAccessGate();
if (blocked) return <MyOwnEmptyState reason={reason} />;
```

## The feature gate

Per-feature, read from the subscribed plan's `features`.

```tsx
import { ConnectFeatureGate } from "@cosmoops/connect-sdk/client";

<ConnectFeatureGate feature="advanced-reporting" fallback={<UpgradePrompt />}>
  <AdvancedReports />
</ConnectFeatureGate>
```

The hook form is usually better inside a component that is already rendering —
hiding one button does not need a whole gate:

```tsx
const canExport = useConnectFeature("csv-export");

<Button isDisabled={!canExport}>Export CSV</Button>
```

<Callout title="Disable, don't hide">
  A hidden feature is indistinguishable from a missing one, and the user never
  learns an upgrade would give it to them. Disable it with a reason; hide only
  what would be meaningless to them.
</Callout>

<Preview name="feature-gate">

```tsx
import { ConnectFeatureGate } from "@cosmoops/connect-sdk/client";

<ConnectFeatureGate feature="reporting">
  <DeliveryReports />
</ConnectFeatureGate>
```

</Preview>

## The subscription gate

Narrower than the access gate: it asks only about the subscription, ignoring
verification, onboarding and payment state.

Reach for it on a screen that is *about* subscribing — a pricing page, an
upgrade flow — where the access gate's "you have no plan" screen would be
telling the user something they are already in the middle of fixing.

```tsx
<ConnectSubscriptionGate fallback={<ConnectPlansPage />}>
  <BillingSettings />
</ConnectSubscriptionGate>
```

<Preview name="subscription-gate">

```tsx
<ConnectSubscriptionGate>
  <BillingSettings />
</ConnectSubscriptionGate>
```

</Preview>

## The workspace-type gate

Some screens only make sense for a team: a members table, seat management, an
invite flow. An `individual` workspace has exactly one seat and can never have
another, so those screens are not "empty" for them — they are meaningless.

```tsx
<ConnectWorkspaceTypeGate type="business" fallback={<ConvertToBusinessPrompt />}>
  <TeamSettings />
</ConnectWorkspaceTypeGate>
```

A workspace can convert from individual to business under a live session, so
read it per render rather than caching it past sign-in.

<Preview name="workspace-type-gate">

```tsx
<ConnectWorkspaceTypeGate type="business" fallback={<ConvertToBusinessPrompt />}>
  <TeamSettings />
</ConnectWorkspaceTypeGate>
```

</Preview>

## Choosing

Walk down this list and stop at the first that fits:

<Steps>

<Step>
### Is the whole application unusable right now?

`ConnectAccessGate` — and it is already on, so there is nothing to do.
</Step>

<Step>
### Is this one screen about a capability their plan may not include?

`ConnectFeatureGate`, or `useConnectFeature` for a single control.
</Step>

<Step>
### Is this screen only meaningful with a subscription, and is it *not* the page that sells one?

`ConnectSubscriptionGate`.
</Step>

<Step>
### Does this screen only make sense for a team?

`ConnectWorkspaceTypeGate`.
</Step>

<Step>
### None of the above?

No gate. Most screens need none — the access gate in the shell has already
answered the only question that applies to all of them.
</Step>

</Steps>