Generated from `@cosmoops/connect-sdk/client`'s TypeScript source and its own doc comments - see [Reference](/docs/reference) for the other entry points. Regenerated on every deploy; don't hand-edit.

## Interfaces

### Interface: AppShellProps [#interface-appshellprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `name` | `string` | This application's name, in the `cosmo·ops VOICE` lockup. See `ConnectLogo`. |
| `icon?` | `ReactNode` | This application's glyph, drawn on the CosmoOps badge. Falls back to the Connect rings. |
| `homeHref?` | `string` | Where the lockup links. Defaults to `/`. |
| `nav` | [`ConnectNav`](#type-alias-connectnav) | The sidebar's destinations - items, sections, or a mix. See `ConnectNav`. |
| `secondaryNav?` | [`ConnectNav`](#type-alias-connectnav) | A second nav group, pinned to the foot of the rail above the account row. For the routes a product has but does not want competing with its own: settings, help, changelog, status. They are the same `ConnectNav` shape and get the same active resolution and audience filtering - the only difference is where they sit, which is the difference between "what this product is for" and "everything else". A route belongs up top if a user goes there to *do the job*, and down here if they go there to change how the job works. Putting Settings in the primary list is what makes a nine-item rail out of a five-item product. |
| `activePath?` | `string` | Current path. Pass `usePathname()` in Next; see `ConnectSidebarNavProps.activePath`. |
| `renderLink?` | [`ConnectLinkRenderer`](#type-alias-connectlinkrenderer) | See `ConnectLinkRenderer` - how this app renders a link. Defaults to a plain `<a>`. |
| `sidebarFooter?` | `ReactNode` | Extra rows pinned to the bottom of the sidebar, above the account row. See `ConnectSidebarRow`. |
| `workspaceSwitcher?` | \| `false` \| \{ `alwaysShow?`: `boolean`; \} | The workspace switcher under the brand. On by default, and it hides itself for an account with only one workspace - so the reason to pass `false` is not "most users have one" (already handled) but "this application renders its own". Pass `{ alwaysShow: true }` instead of `true` to keep it mounted below two workspaces too - see `ConnectWorkspaceSwitcherProps.alwaysShow` for what that changes about the dropdown. |
| `footer?` | `ReactNode` | Rendered below `children`, inside the scrolling content pane. |
| `defaultCollapsed?` | `boolean` | Start the rail collapsed. **Defaults to `true`** - see the component's own doc comment. Ignored when `collapsed` is supplied, and overridden by a stored preference from the user's last visit. |
| `collapsed?` | `boolean` | Drive the rail yourself. Supply `onCollapsedChange` with it. |
| `onCollapsedChange?` | (`collapsed`: `boolean`) => `void` | - |
| `collapsible?` | `boolean` | Remove the collapse control and pin the rail expanded. |
| `showAppsGrid?` | `boolean` | The app launcher, in the sidebar footer. On by default. |
| `showAuthButtons?` | `boolean` | "Continue with Connect" / "Create account" in the sidebar footer when signed out. On by default. |
| `theme?` | [`ThemeMode`](/docs/reference/core#type-alias-thememode) | Passed to `ConnectUserButton` - the SDK has no theme provider, so this is host-controlled. |
| `onThemeChange?` | (`theme`: [`ThemeMode`](/docs/reference/core#type-alias-thememode)) => `void` | - |
| `gate?` | `boolean` | Wrap `children` in `ConnectAccessGate` - suspended, no plan, onboarding pending, payment due, decided once for the whole app. **On by default**, because the shell is exactly the "wrap your app content once" place that gate asks for, and an app that mounts a shell and forgets the gate is an app that lets a suspended workspace keep working. The chrome is never gated: the sidebar, the lockup, the account row and sign-out stay reachable in every blocked state, so a blocked customer can always get to the page that unblocks them. Routes flagged `gateExempt` on their nav item render outside it - see `ConnectNavItem.gateExempt` and `gateExemptRoutes`. |
| `gateExemptRoutes?` | `string`[] | Extra ungated paths, on top of the ones flagged `gateExempt` in `nav`. For a route that is not in the sidebar at all - a `/checkout/callback`, say. Prefix-matched, same as the nav's active resolution. |
| `askAi?` | `boolean` \| [`ConnectAskAiProps`](#interface-connectaskaiprops) | Mount `ConnectAskAi` - a chat panel grounded in the signed-in workspace's Context Index, streamed through your own bridge. Pass an object to override its heading, copy or markdown plugins. **On by default**, like `gate`, and for the same reason: a family of application platforms should answer questions the same way, and a feature every partner has to remember to switch on is a feature most of them do not have. It stays inert until it can actually work - the trigger renders nothing for a signed-out visitor, and a workspace whose Context Index is off gets a message in the panel rather than a broken answer. Set `false` for an application that should not offer it at all. |
| `className?` | `string` | - |
| `contentClassName?` | `string` | Classes on the scrolling content pane - override the default max-width and padding here. |
| `children` | `ReactNode` | - |

***

### Interface: BookingsQuery [#interface-bookingsquery]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `from` | `Date` | - |
| `to` | `Date` | - |
| `hostMemberId?` | `string` | Only appointments this member is hosting. Omit for the whole workspace. |
| `limit?` | `number` | - |

***

### Interface: CheckoutUrlOptions [#interface-checkouturloptions]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `appUrl` | `string` | Connect's origin, from `useConnect().appUrl`. |
| `applicationId` | `string` | - |
| `planId` | `string` | - |
| `subscriptionId?` | `string` \| `null` | The live subscription's id, when there is one. Present means "change the plan on this subscription"; absent means "subscribe for the first time" - and the two are separate screens in Connect, because only one of them can price proration against a cycle already paid for. Pass it only when you know **which plan the subscription is on today**, which is what makes the change classifiable. `checkoutSubscriptionId` derives it from the action you are already showing on the button; a bare `subscription?.subscription?.id` does not, and a live subscription whose plan has been deleted from the catalogue is the case where the two differ and the change-plan screen answers `notFound()`. Leaving it `null` is always safe: the subscribe route redirects to the change-plan screen itself when Connect finds a subscription to change. |
| `coupon?` | `string` \| `null` | Carried into Connect's checkout, where it is validated against the real cart. |
| `seats?` | `number` \| `null` | Seats to subscribe with on a per-seat plan - the total, not extras. Seeds Connect's checkout stepper, where the plan's included count is the floor. Lets "Subscribe for my 8-person team" price eight seats in one payment rather than the included count now and a prorated top-up after. Ignored on a plan change (seats are changed on the subscription) and on plans that do not sell seats one at a time. |
| `next?` | `string` \| `null` | Where Connect sends the customer once the subscription is live - an absolute URL on your own origin. **Without it they do not come back.** Connect's default destination is the new subscription's own billing page, which is the right answer for someone who started in Connect and the wrong one for someone who started on your pricing page: they pay, land on a billing screen for a product they were in the middle of adopting, and nothing on it points home. Connect validates the origin against the redirect URIs registered for your Connect client and silently ignores anything else, so a value it does not recognise costs the return trip and never redirects your customer somewhere you did not register. Register the origin you intend to use; the path and query are yours. |

***

### Interface: ComparablePlan [#interface-comparableplan]

The minimum a plan-like object has to expose to be grouped or compared -
lets the dialog pass a `SubscribedPlan` (which carries no add-ons or
features) wherever a comparison is wanted.

#### Properties

| Property | Type |
| ------ | ------ |
| `name` | `string` |
| `price` | `number` |
| `billingInterval` | [`BillingInterval`](/docs/reference/core#type-alias-billinginterval) |
| `planGroup?` | `string` \| `null` |

***

### Interface: ConnectAccessGateProps [#interface-connectaccessgateprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `children` | `ReactNode` | - |
| `fallback?` | `ReactNode` | Replaces the default card for every hard-block state (`suspended`/`no_subscription`/`onboarding_pending`/`blocked`) uniformly. For a different card per state, call `useConnectAccessGate()` directly instead. |
| `loading?` | `ReactNode` | Shown while the first read is in flight, **in place of** `children`. Opt-in, and rarely what you want. The default is to render `children` during that window - see the component's doc comment on why a blanket placeholder over a whole application is worse than the flash it avoids. Pass this only for a gate wrapped around one small region whose blocked and unblocked states differ enough that showing the unblocked one first would mislead. |
| `className?` | `string` | - |

***

### Interface: ConnectAccessGateResult [#interface-connectaccessgateresult]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `state` | [`ConnectAccessGateState`](#type-alias-connectaccessgatestate) | - |
| `blocker` | [`ReadinessBlocker`](/docs/reference/core#interface-readinessblocker) \| `null` | The blocker driving a hard-block state (`suspended`/`no_subscription`/`onboarding_pending`/`blocked`). `null` otherwise. |
| `paymentRetry` | [`ReadinessBlocker`](/docs/reference/core#interface-readinessblocker) \| `null` | The `payment_retry_pending` blocker, only when `state === "payment_retry"`. Carries `graceEndsAt`. |

#### Methods

##### refresh()

```ts
refresh(): void;
```

Re-reads entitlements, evicting the cached answer first.

Exposed because a blocker is almost always resolved **somewhere this tab
cannot see** - on Connect, in another tab or on a phone - and nothing
about that reaches this one. The gate wires it to the tab regaining
focus; call it yourself if your app learns of a change some other way.

###### Returns

`void`

***

### Interface: ConnectAccountSessionsProps [#interface-connectaccountsessionsprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `className?` | `string` | - |
| `maxVisible?` | `number` | Rows shown before the "Show all" toggle collapses the rest. An account with a dozen devices would otherwise push everything below it - the sign-out action included - off the bottom of a modal. Pass `0` to always render every row. |

***

### Interface: ConnectAppsGridProps [#interface-connectappsgridprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `className?` | `string` | Extra classes on the trigger button. |
| `label?` | `string` | Trigger label for screen readers, and the collapsed rail's tooltip. |
| `variant?` | `"icon"` \| `"row"` | How the trigger is drawn. - `"row"` - a full-width sidebar row, glyph on the rail's shared 32px centre. The default, since the launcher lives in the sidebar footer now that there is no top bar to hold it. - `"icon"` - the square accent button, for a bar of your own. |

***

### Interface: ConnectAskAiProps [#interface-connectaskaiprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `screens?` | readonly [`ConnectScreen`](/docs/reference/core#interface-connectscreen)[] | The screens in your application, described for the assistant. Without these it answers from general knowledge about software, because Connect has no idea what your product is - see `ConnectScreen`. With them, "what can I do here?" answers in your own words, the empty panel offers your own opening questions, and `showActions` can link into your routes. Prose and links only: Connect never uses them to decide what anyone may do, and never executes anything on your behalf. |
| `activePath?` | `string` | The route the user is currently on. **Usually omit this.** Left unset, the panel resolves the live path itself through `useActivePath`, which reads `location.pathname` from a `useSyncExternalStore` whose server snapshot is empty - so it works inside a shared App Shell, where `usePathname()` is refused outright (`CLIENT_HOOK_DYNAMIC`) because URL data cannot live in an artifact shared across URLs. That is the same reason `ConnectAppShell` recommends leaving its own `activePath` unset in a Next app. Pass it only where your router's path is not `location.pathname` - a hash router, or a panel embedded under a basePath your routes do not include. |
| `renderLink?` | (`props`: \{ `href`: `string`; `className`: `string`; `children`: `ReactNode`; \}) => `ReactNode` | Renders an action link with your router, so following one is a client transition rather than a document load. Omit it and links are plain anchors, which work everywhere. `renderLink={({ href, className, children }) => ( <Link href={href} className={className}>{children}</Link> )}` |
| `title?` | `string` | Panel heading. Defaults to `"Ask AI"`. |
| `assistantName?` | `string` | Byline over each assistant turn. Defaults to `"Assistant"`. |
| `placeholder?` | `string` | Composer placeholder. |
| `hint?` | `string` | Shown once, above an empty conversation. |
| `defaultOpen?` | `boolean` | Start open. The panel owns its state after that. |
| `markdownPlugins?` | `PluginConfig` | Streamdown plugins - syntax highlighting, KaTeX, Mermaid, CJK. Off unless you pass them, because each is a package of its own and several are large (Mermaid alone dwarfs this SDK). Prose, GFM tables, task lists and plain code fences all render without any of them. `import { code } from "@streamdown/code"; import { mermaid } from "@streamdown/mermaid"; <ConnectAskAi markdownPlugins={{ code, mermaid }} />` Hoist the object to module scope: Streamdown compares this prop by reference, so an inline literal defeats its block memoisation. |

***

### Interface: ConnectAuthButtonProps [#interface-connectauthbuttonprops]

#### Extends

- `Omit`\<`ButtonProps`, `"onPress"` \| `"children"`\>

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `variant?` | \| `"danger"` \| `"primary"` \| `"secondary"` \| `"danger-soft"` \| `"ghost"` \| `"outline"` \| `"tertiary"` | - | `ConnectAuthButtonProps`.`variant` |
| `fullWidth?` | `boolean` | - | `ConnectAuthButtonProps`.`fullWidth` |
| `isIconOnly?` | `boolean` | - | `ConnectAuthButtonProps`.`isIconOnly` |
| `size?` | `"lg"` \| `"md"` \| `"sm"` | - | `ConnectAuthButtonProps`.`size` |
| `children?` | `ReactNode` | Defaults to "Continue with Connect" / "Sign up with Connect". | - |
| `__button_group_child?` | `boolean` | - | `Omit.__button_group_child` |
| `aria-label?` | `string` | Defines a string value that labels the current element. | `Omit.aria-label` |
| `aria-labelledby?` | `string` | Identifies the element (or elements) that labels the current element. | `Omit.aria-labelledby` |
| `aria-describedby?` | `string` | Identifies the element (or elements) that describes the object. | `Omit.aria-describedby` |
| `aria-details?` | `string` | Identifies the element (or elements) that provide a detailed, extended description for the object. | `Omit.aria-details` |
| `id?` | `string` | The element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id). | `Omit.id` |
| `excludeFromTabOrder?` | `boolean` | Whether to exclude the element from the sequential tab order. If true, the element will not be focusable via the keyboard by tabbing. This should be avoided except in rare scenarios where an alternative means of accessing the element or its functionality via the keyboard is available. | `Omit.excludeFromTabOrder` |
| `dir?` | `string` | - | `Omit.dir` |
| `lang?` | `string` | - | `Omit.lang` |
| `hidden?` | `boolean` | - | `Omit.hidden` |
| `inert?` | `boolean` | - | `Omit.inert` |
| `translate?` | `"yes"` \| `"no"` | - | `Omit.translate` |
| `onClickCapture?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onClickCapture` |
| `onAuxClick?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onAuxClick` |
| `onAuxClickCapture?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onAuxClickCapture` |
| `onContextMenu?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onContextMenu` |
| `onContextMenuCapture?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onContextMenuCapture` |
| `onDoubleClick?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onDoubleClick` |
| `onDoubleClickCapture?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onDoubleClickCapture` |
| `onMouseDown?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseDown` |
| `onMouseDownCapture?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseDownCapture` |
| `onMouseEnter?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseEnter` |
| `onMouseLeave?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseLeave` |
| `onMouseMove?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseMove` |
| `onMouseMoveCapture?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseMoveCapture` |
| `onMouseOut?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseOut` |
| `onMouseOutCapture?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseOutCapture` |
| `onMouseOver?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseOver` |
| `onMouseOverCapture?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseOverCapture` |
| `onMouseUp?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseUp` |
| `onMouseUpCapture?` | `MouseEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onMouseUpCapture` |
| `onTouchCancel?` | `TouchEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTouchCancel` |
| `onTouchCancelCapture?` | `TouchEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTouchCancelCapture` |
| `onTouchEnd?` | `TouchEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTouchEnd` |
| `onTouchEndCapture?` | `TouchEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTouchEndCapture` |
| `onTouchMove?` | `TouchEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTouchMove` |
| `onTouchMoveCapture?` | `TouchEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTouchMoveCapture` |
| `onTouchStart?` | `TouchEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTouchStart` |
| `onTouchStartCapture?` | `TouchEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTouchStartCapture` |
| `onPointerDown?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerDown` |
| `onPointerDownCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerDownCapture` |
| `onPointerMove?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerMove` |
| `onPointerMoveCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerMoveCapture` |
| `onPointerUp?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerUp` |
| `onPointerUpCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerUpCapture` |
| `onPointerCancel?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerCancel` |
| `onPointerCancelCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerCancelCapture` |
| `onPointerEnter?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerEnter` |
| `onPointerLeave?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerLeave` |
| `onPointerOver?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerOver` |
| `onPointerOverCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerOverCapture` |
| `onPointerOut?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerOut` |
| `onPointerOutCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onPointerOutCapture` |
| `onGotPointerCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onGotPointerCapture` |
| `onGotPointerCaptureCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onGotPointerCaptureCapture` |
| `onLostPointerCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onLostPointerCapture` |
| `onLostPointerCaptureCapture?` | `PointerEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onLostPointerCaptureCapture` |
| `onScroll?` | `UIEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onScroll` |
| `onScrollCapture?` | `UIEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onScrollCapture` |
| `onWheel?` | `WheelEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onWheel` |
| `onWheelCapture?` | `WheelEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onWheelCapture` |
| `onAnimationStart?` | `AnimationEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onAnimationStart` |
| `onAnimationStartCapture?` | `AnimationEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onAnimationStartCapture` |
| `onAnimationEnd?` | `AnimationEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onAnimationEnd` |
| `onAnimationEndCapture?` | `AnimationEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onAnimationEndCapture` |
| `onAnimationIteration?` | `AnimationEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onAnimationIteration` |
| `onAnimationIterationCapture?` | `AnimationEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onAnimationIterationCapture` |
| `onTransitionCancel?` | `TransitionEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTransitionCancel` |
| `onTransitionCancelCapture?` | `TransitionEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTransitionCancelCapture` |
| `onTransitionEnd?` | `TransitionEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTransitionEnd` |
| `onTransitionEndCapture?` | `TransitionEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTransitionEndCapture` |
| `onTransitionRun?` | `TransitionEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTransitionRun` |
| `onTransitionRunCapture?` | `TransitionEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTransitionRunCapture` |
| `onTransitionStart?` | `TransitionEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTransitionStart` |
| `onTransitionStartCapture?` | `TransitionEventHandler`\<`HTMLButtonElement`\> | - | `Omit.onTransitionStartCapture` |
| `onKeyDown?` | (`e`: `KeyboardEvent`) => `void` | Handler that is called when a key is pressed. | `Omit.onKeyDown` |
| `onKeyUp?` | (`e`: `KeyboardEvent`) => `void` | Handler that is called when a key is released. | `Omit.onKeyUp` |
| `onFocus?` | (`e`: `FocusEvent`\<`Element`\>) => `void` | Handler that is called when the element receives focus. | `Omit.onFocus` |
| `onBlur?` | (`e`: `FocusEvent`\<`Element`\>) => `void` | Handler that is called when the element loses focus. | `Omit.onBlur` |
| `onFocusChange?` | (`isFocused`: `boolean`) => `void` | Handler that is called when the element's focus status changes. | `Omit.onFocusChange` |
| `onHoverStart?` | (`e`: `HoverEvent`) => `void` | Handler that is called when a hover interaction starts. | `Omit.onHoverStart` |
| `onHoverEnd?` | (`e`: `HoverEvent`) => `void` | Handler that is called when a hover interaction ends. | `Omit.onHoverEnd` |
| `onHoverChange?` | (`isHovering`: `boolean`) => `void` | Handler that is called when the hover state changes. | `Omit.onHoverChange` |
| `onPressStart?` | (`e`: `PressEvent`) => `void` | Handler that is called when a press interaction starts. | `Omit.onPressStart` |
| `onPressEnd?` | (`e`: `PressEvent`) => `void` | Handler that is called when a press interaction ends, either over the target or when the pointer leaves the target. | `Omit.onPressEnd` |
| `onPressChange?` | (`isPressed`: `boolean`) => `void` | Handler that is called when the press state changes. | `Omit.onPressChange` |
| `onPressUp?` | (`e`: `PressEvent`) => `void` | Handler that is called when a press is released over the target, regardless of whether it started on the target or not. | `Omit.onPressUp` |
| `onClick?` | (`e`: `MouseEvent`\<`FocusableElement`\>) => `void` | **Not recommended – use `onPress` instead.** `onClick` is an alias for `onPress` provided for compatibility with other libraries. `onPress` provides additional event details for non-mouse interactions. | `Omit.onClick` |
| `autoFocus?` | `boolean` | Whether the element should receive focus on render. | `Omit.autoFocus` |
| `key?` | `Key` \| `null` | - | `Omit.key` |
| `ref?` | `Ref`\<`HTMLButtonElement`\> | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). **See** [React Docs](https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom) | `Omit.ref` |
| `isDisabled?` | `boolean` | Whether the button is disabled. | `Omit.isDisabled` |
| `aria-disabled?` | `boolean` \| `"true"` \| `"false"` | Indicates whether the element is disabled to users of assistive technology. | `Omit.aria-disabled` |
| `aria-expanded?` | `boolean` \| `"true"` \| `"false"` | Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed. | `Omit.aria-expanded` |
| `aria-haspopup?` | \| `boolean` \| `"true"` \| `"false"` \| `"dialog"` \| `"grid"` \| `"listbox"` \| `"menu"` \| `"tree"` | Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element. | `Omit.aria-haspopup` |
| `aria-controls?` | `string` | Identifies the element (or elements) whose contents or presence are controlled by the current element. | `Omit.aria-controls` |
| `aria-pressed?` | `boolean` \| `"true"` \| `"false"` \| `"mixed"` | Indicates the current "pressed" state of toggle buttons. | `Omit.aria-pressed` |
| `aria-current?` | `boolean` \| `"date"` \| `"location"` \| `"true"` \| `"false"` \| `"time"` \| `"page"` \| `"step"` | Indicates whether this element represents the current item within a container or set of related elements. | `Omit.aria-current` |
| `type?` | `"reset"` \| `"submit"` \| `"button"` | The behavior of the button when used in an HTML form. **Default** `'button'` | `Omit.type` |
| `preventFocusOnPress?` | `boolean` | Whether to prevent focus from moving to the button when pressing it. Caution, this can make the button inaccessible and should only be used when alternative keyboard interaction is provided, such as ComboBox's MenuTrigger or a NumberField's increment/decrement control. | `Omit.preventFocusOnPress` |
| `form?` | `string` | The `<form>` element to associate the button with. The value of this attribute must be the id of a `<form>` in the same document. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#form). | `Omit.form` |
| `formAction?` | `string` \| ((`formData`: `FormData`) => `void` \| `Promise`\<`void`\>) | The URL that processes the information submitted by the button. Overrides the action attribute of the button's form owner. | `Omit.formAction` |
| `formEncType?` | `string` | Indicates how to encode the form data that is submitted. | `Omit.formEncType` |
| `formMethod?` | `string` | Indicates the HTTP method used to submit the form. | `Omit.formMethod` |
| `formNoValidate?` | `boolean` | Indicates that the form is not to be validated when it is submitted. | `Omit.formNoValidate` |
| `formTarget?` | `string` | Overrides the target attribute of the button's form owner. | `Omit.formTarget` |
| `name?` | `string` | Submitted as a pair with the button's value as part of the form data. | `Omit.name` |
| `value?` | `string` | The value associated with the button's name when it's submitted with the form data. | `Omit.value` |
| `className?` | `ClassNameOrFunction`\<`ButtonRenderProps`\> | The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. A function may be provided to compute the class based on component state. **Default** `'react-aria-Button'` | `Omit.className` |
| `isPending?` | `boolean` | Whether the button is in a pending state. This disables press and hover events while retaining focusability, and announces the pending state to screen readers. | `Omit.isPending` |
| `style?` | `StyleOrFunction`\<`ButtonRenderProps`\> | The inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element. A function may be provided to compute the style based on component state. | `Omit.style` |
| `slot?` | `string` \| `null` | A slot name for the component. Slots allow the component to receive props from a parent component. An explicit `null` value indicates that the local props completely override all props received from a parent. | `Omit.slot` |
| `render?` | `DOMRenderFunction`\<`"button"`, `ButtonRenderProps`\> | Overrides the default DOM element with a custom render function. This allows rendering existing components with built-in styles and behaviors such as router links, animation libraries, and pre-styled components. Requirements: - You must render the expected element type (e.g. if `<button>` is expected, you cannot render an `<a>`). - Only a single root DOM element can be rendered (no fragments). - You must pass through props and ref to the underlying DOM element, merging with your own prop as appropriate. | `Omit.render` |

***

### Interface: ConnectAvailabilityFormModalProps [#interface-connectavailabilityformmodalprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `isOpen` | `boolean` | - |
| `onOpenChange` | (`isOpen`: `boolean`) => `void` | - |
| `scheduleId?` | `string` \| `null` | `null` (or omitted) creates a new schedule. A row id from `useAvailabilitySchedules` edits that one instead - name and hours prefill from it. |
| `onSaved?` | (`schedule`: [`AvailabilitySchedule`](/docs/reference/core#interface-availabilityschedule)) => `void` | Called once the schedule is created or saved, before the modal closes. |

***

### Interface: ConnectBookingCalendarProps [#interface-connectbookingcalendarprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `timeZone?` | `string` | The business's IANA timezone. **Every instant on the grid is read against it, never the viewer's** - a workspace's working day must not shift because a member is travelling. Defaults to the host's zone, which is right for the common case where staff and business share one. Pass it explicitly when they might not. |
| `hostMemberId?` | `string` | Only appointments this member hosts. Omit for the whole workspace's diary. |
| `window?` | [`GridWindow`](#interface-gridwindow) | Narrow the visible band - see `DEFAULT_WINDOW`. |
| `canBook?` | `boolean` | Hide the "New appointment" action for a read-only view. |
| `className?` | `string` | - |

***

### Interface: ConnectCallButtonProps [#interface-connectcallbuttonprops]

The number to ring, and how to name the person on the in-call bar.

#### Extends

- [`ConnectCallTarget`](#interface-connectcalltarget)

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `phone` | `string` | E.164, e.g. `+919876543210`. | [`ConnectCallTarget`](#interface-connectcalltarget).[`phone`](#interface-connectcalltarget) |
| `name?` | `string` | Shown on the in-call bar. Defaults to the number. | [`ConnectCallTarget`](#interface-connectcalltarget).[`name`](#interface-connectcalltarget) |
| `purpose?` | [`ConnectCallPurpose`](/docs/reference/core#type-alias-connectcallpurpose) | What the call is for - Connect places it from a number allowed to carry it. See `ConnectCallPurpose`; omit it to place the call as before. | [`ConnectCallTarget`](#interface-connectcalltarget).[`purpose`](#interface-connectcalltarget) |
| `onEnded?` | (`result`: \{ `callId`: `string` \| `null`; \}) => `void` | Called once the call is over, with Connect's id for it - or `null` when it never got as far as being approved. Read the outcome on your server with `connect.calls.get(callId)`; see the module comment for why not here. | [`ConnectCallTarget`](#interface-connectcalltarget).[`onEnded`](#interface-connectcalltarget) |
| `variant?` | `"default"` \| `"compact"` | `compact` is an icon-only button for a row beside a number. | - |
| `children?` | `ReactNode` | Button label. Defaults to "Call". | - |
| `isDisabled?` | `boolean` | - | - |
| `className?` | `string` | Merged with `cn`, so an override replaces the default it conflicts with. | - |

***

### Interface: ConnectCallContextValue [#interface-connectcallcontextvalue]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `isBusy` | `boolean` | A call is in progress - every Call button disables on it. |
| `availability` | \| [`CallingAvailability`](/docs/reference/core#interface-callingavailability) \| `null` | Whether this member can call and, if not, why - `null` while it loads. Read once by the provider and shared, so every button and status panel agrees, and refreshed after each call so the minutes shown keep up. |

#### Methods

##### startCall()

```ts
startCall(target: ConnectCallTarget): Promise<void>;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `target` | [`ConnectCallTarget`](#interface-connectcalltarget) |

###### Returns

`Promise`\<`void`\>

***

##### refreshAvailability()

```ts
refreshAvailability(): void;
```

Re-read `availability` - after a top-up in another tab, say.

###### Returns

`void`

***

### Interface: ConnectCallingStatusProps [#interface-connectcallingstatusprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `className?` | `string` | Merged with `cn`. |

***

### Interface: ConnectCallProviderProps [#interface-connectcallproviderprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `children` | `ReactNode` | - |
| `onCallEnded?` | (`result`: \{ `callId`: `string` \| `null`; `phone`: `string`; \}) => `void` | Called for every call that ends, whichever button started it - the place to wire "log this call" once for the whole app. A button's own `onEnded` runs as well. |
| `onError?` | (`message`: `string`) => `void` | Called when a call could not be placed, with the reason to show. Defaults to the in-call bar saying so. |
| `className?` | `string` | Extra classes for the in-call bar's outer element - merged with `cn`. |

***

### Interface: ConnectCallTarget [#interface-connectcalltarget]

The number to ring, and how to name the person on the in-call bar.

#### Extended by

- [`ConnectCallButtonProps`](#interface-connectcallbuttonprops)

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `phone` | `string` | E.164, e.g. `+919876543210`. |
| `name?` | `string` | Shown on the in-call bar. Defaults to the number. |
| `purpose?` | [`ConnectCallPurpose`](/docs/reference/core#type-alias-connectcallpurpose) | What the call is for - Connect places it from a number allowed to carry it. See `ConnectCallPurpose`; omit it to place the call as before. |
| `onEnded?` | (`result`: \{ `callId`: `string` \| `null`; \}) => `void` | Called once the call is over, with Connect's id for it - or `null` when it never got as far as being approved. Read the outcome on your server with `connect.calls.get(callId)`; see the module comment for why not here. |

***

### Interface: ConnectCheckoutState [#interface-connectcheckoutstate]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `status` | [`ConnectCheckoutStatus`](#type-alias-connectcheckoutstatus) | - |
| `url` | `string` \| `null` | The Connect URL of the run that produced this state - render it as a fallback link when `status` is `"blocked"`. |

***

### Interface: ConnectConfirmDialogProps [#interface-connectconfirmdialogprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `trigger` | `ReactNode` | The control that opens it, and it **must be a pressable** - a HeroUI `Button`, or anything else built on React Aria's `useButton`. It is rendered as a direct child of the `Modal` root, which is React Aria's `DialogTrigger`: that puts a `PressResponder` around its children and the trigger registers with it. A plain `<span>` never registers, and React Aria warns that the responder has no pressable child. |
| `title` | `ReactNode` | - |
| `description?` | `ReactNode` | What will happen, in the user's words. Name the thing being acted on. |
| `confirmLabel?` | `string` | - |
| `cancelLabel?` | `string` | - |
| `tone?` | `"danger"` \| `"primary"` | `danger` for anything that destroys data. |
| `onConfirm` | () => `void` \| `Promise`\<`void`\> | - |

***

### Interface: ConnectContextValue [#interface-connectcontextvalue]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `clientId` | `string` | Public OAuth client id - the `clientId` passed to `<ConnectProvider>`, else the one your bridge reports on `/session`, else an empty string (an older bridge, before the session read settles). Safe in a browser regardless: it only ever goes into Connect URLs. Most integrations never read this - it exists for the explicit-`redirectUri` flow and for `connectPortalUrl`'s way back. |
| `appUrl` | `string` | Connect's own origin, for deep links out to Profile, Billing and the catalog. |
| `handoffMode` | [`ConnectHandoffMode`](#type-alias-connecthandoffmode) | How `openConnect` sends people to Connect - `ConnectProviderProps.handoff`, after defaulting. |
| `session` | [`ConnectSession`](#interface-connectsession) \| `null` | - |
| `isLoading` | `boolean` | `true` until the first `/session` read settles. Render skeletons, not a Login button. |
| `error` | `string` \| `null` | - |

#### Methods

##### openConnect()

```ts
openConnect(target: string, options?: OpenConnectOptions): Window | null;
```

Sends the visitor to a Connect page for a task, and brings them back when
it is done. **Call it from a press handler** - a popup opened after an
`await` is what pop-up blockers exist to stop.

`target` is a path (`"/billing"`) or an absolute Connect URL, such as a
readiness blocker's `resolveUrl`. Anything on another origin throws.

- `"popup"` mode opens Connect in a new tab (or a sized window, with
  `features`), which closes itself when the task is done and tells this
  page first - every Connect component on it then re-reads. Returns the
  window, for a caller that wants to watch it.
- `"redirect"` mode, or a popup the browser blocked, navigates this tab
  to Connect, which navigates back with `?connect_result=`. Returns
  `null`; the page is going away.

Either way the way back carries this page's URL and this application's
`client_id`, and Connect only honours an origin registered for that
client - register your app's origin among its redirect URIs.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `target` | `string` |
| `options?` | [`OpenConnectOptions`](#interface-openconnectoptions) |

###### Returns

`Window` \| `null`

***

##### signIn()

```ts
signIn(options?: {
  prompt?: "create";
}): void;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `options?` | \{ `prompt?`: `"create"`; \} |
| `options.prompt?` | `"create"` |

###### Returns

`void`

***

##### signOut()

```ts
signOut(): Promise<void>;
```

###### Returns

`Promise`\<`void`\>

***

##### refresh()

```ts
refresh(): Promise<void>;
```

###### Returns

`Promise`\<`void`\>

***

##### fetchResource()

```ts
fetchResource<T>(path: string): Promise<T>;
```

GET through the bridge, relative to `basePath`. Shared cache, deduped in flight.

###### Type Parameters

| Type Parameter |
| ------ |
| `T` |

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `path` | `string` |

###### Returns

`Promise`\<`T`\>

***

##### mutateResource()

```ts
mutateResource<T>(
   path: string, 
   body?: unknown, 
method?: "POST" | "DELETE"): Promise<T>;
```

POST through the bridge, relative to `basePath`. Unlike `signOut`
(which deliberately swallows a failed logout so the local UI doesn't
hang on a Connect outage), this throws on a non-2xx response - a caller
mutating state needs to know when it didn't take.

Evicts only `path`'s own cache entry, not the whole shared cache - a
blunt full clear would break `fetchResource`'s in-flight dedup for
every *other* resource mounted on the same page. A component that reads
`path` through `useConnectResource` still needs to call that hook's own
`refetch()` afterward; clearing the cache entry alone does not re-render
anything by itself.

`method` widens this to the one other verb the bridge answers, `DELETE`,
for the routes where removal is the whole operation and a body would be
ceremony - Ask AI's history is the first. It stays a two-value union
rather than `string`: everything else on this bridge is a POST, and a
free-form verb here would be an invitation to invent REST semantics the
bridge's exact-match route table does not have.

###### Type Parameters

| Type Parameter |
| ------ |
| `T` |

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `path` | `string` |
| `body?` | `unknown` |
| `method?` | `"POST"` \| `"DELETE"` |

###### Returns

`Promise`\<`T`\>

***

##### invalidateResource()

```ts
invalidateResource(path: string): void;
```

Drops one path's cached read so the next `fetchResource` for it goes to
the bridge again.

`useConnectResource`'s own `refetch()` is not enough on its own: it bumps
a version counter to re-run that hook's effect, and the effect calls
`fetchResource`, which hands back the *same* cached promise. That is
correct after a `mutateResource` (which evicts the entry itself), but a
caller polling for a change made somewhere else entirely - a checkout
completed in a Connect tab, say - has no mutation to piggyback on. So
invalidate, then refetch.

Scoped to one path for the same reason `mutateResource` is: clearing the
whole cache would break in-flight dedup for every other resource on the
page.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `path` | `string` |

###### Returns

`void`

***

##### streamResource()

```ts
streamResource(
   path: string, 
   body: unknown, 
   options?: {
  signal?: AbortSignal;
}): Promise<Response>;
```

POST through the bridge and hand back the raw `Response`, unread and
uncached - the seam `<ConnectAskAi>` streams server-sent events over.

Separate from `mutateResource` rather than a flag on it, because the two
differ in every respect that matters: this one must not `await
response.json()` (that buffers the whole stream and defeats the point),
must not touch the resource cache (a conversation is not a resource), and
takes an `AbortSignal` so a "Stop" button can actually stop generation.

Throws on a non-2xx, having read the error envelope - which is safe,
because a failure never has a stream behind it.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `path` | `string` |
| `body` | `unknown` |
| `options?` | \{ `signal?`: `AbortSignal`; \} |
| `options.signal?` | `AbortSignal` |

###### Returns

`Promise`\<`Response`\>

***

##### readOneTapHint()

```ts
readOneTapHint(): Promise<ConnectOneTapHint | null>;
```

Asks Connect whether this visitor is already signed in there, without
navigating anywhere.

This is the read `<ConnectOneTap>` is built on, and the reason One Tap
beats `silentSignIn`: one credentialed `fetch` answers the question that
used to cost a full-page redirect to Connect and back.

It only works when the application is served from the **same site** as
Connect - a `cosmoops.com` subdomain. `SameSite` is judged on the
registrable domain, so the visitor's Connect session cookie rides along
from `crm.cosmoops.com` and does not from anywhere else. Off-site
deployments get `signed_out`, which is why every caller treats that answer
as "say nothing" rather than "this visitor has no account".

Resolves to `null` when the bridge offers no One Tap endpoints, or when the
request itself fails - a Connect that cannot be reached says nothing about
whether anyone is signed into it.

###### Returns

`Promise`\<[`ConnectOneTapHint`](#interface-connectonetaphint) \| `null`\>

***

##### oneTapSignIn()

```ts
oneTapSignIn(): Promise<
  | {
  ok: true;
}
  | {
  ok: false;
  reason: ConnectOneTapFailure;
}>;
```

Completes a One Tap sign-in, with no navigation at any point.

Three requests: the bridge mints a PKCE pair and keeps the verifier, Connect
hands back an authorization code, the bridge exchanges it and stores the
session. The page only ever holds the code, which is worthless without the
verifier it never sees.

Refreshes the session on success, so consumers re-render signed in.

###### Returns

`Promise`\<
  \| \{
  `ok`: `true`;
\}
  \| \{
  `ok`: `false`;
  `reason`: [`ConnectOneTapFailure`](#type-alias-connectonetapfailure);
\}\>

***

### Interface: ConnectCreateWorkspaceModalProps [#interface-connectcreateworkspacemodalprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `isOpen` | `boolean` | - |
| `onOpenChange` | (`isOpen`: `boolean`) => `void` | - |
| `onCreated?` | (`workspace`: [`UserWorkspace`](/docs/reference/core#interface-userworkspace)) => `void` | Called once the workspace is created, before the modal closes. |

***

### Interface: ConnectCursorPaginationProps [#interface-connectcursorpaginationprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `shown` | `number` | How many rows the current page is showing. Used for the live-region count. |
| `nextCursor` | `string` \| `null` | The cursor for the next page, or `null` when this is the last one. |
| `hasPrevious` | `boolean` | Whether a previous page exists - the caller owns the cursor history. |
| `onNext` | (`cursor`: `string`) => `void` | - |
| `onPrevious` | () => `void` | - |
| `isPending?` | `boolean` | - |
| `className?` | `string` | Merged onto the root, so a utility here replaces the conflicting default rather than sitting beside it. See `cn`. |
| `noun?` | readonly \[`string`, `string`\] | Singular/plural noun for the count, e.g. `["contact", "contacts"]`. |

***

### Interface: ConnectEntitlements [#interface-connectentitlements]

#### Properties

| Property | Type |
| ------ | ------ |
| `readiness` | [`ReadinessResponse`](/docs/reference/core#interface-readinessresponse) |
| `subscription` | [`SubscriptionResponse`](/docs/reference/core#interface-subscriptionresponse) |

***

### Interface: ConnectEventTypeFormModalProps [#interface-connecteventtypeformmodalprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `isOpen` | `boolean` | - |
| `onOpenChange` | (`isOpen`: `boolean`) => `void` | - |
| `eventTypeId?` | `string` \| `null` | `null` (or omitted) creates a new appointment type. A row id from `useManageableEventTypes` edits that one instead - name, duration, buffers, host, schedule, location and `publicBookingEnabled` all prefill from it. |
| `onSaved?` | (`eventType`: [`ManageableEventType`](/docs/reference/core#interface-manageableeventtype)) => `void` | Called once the appointment type is created or saved, before the modal closes. |

***

### Interface: ConnectFeatureGateProps [#interface-connectfeaturegateprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `feature` | `string` | The feature key, as it appears in `plan.features`. |
| `children` | `ReactNode` | - |
| `fallback?` | `ReactNode` | Rendered when the plan does not include `feature`. Defaults to an upsell card pointing at your plans page - pass `null` to hide the module silently instead. |
| `loading?` | `ReactNode` | Shown while the first read is in flight. Defaults to nothing. |
| `plansHref?` | `string` | Where your plan catalogue is mounted, for the default upsell. |
| `title?` | `string` | Headline for the default upsell - name the feature, not the plan. |
| `description?` | `string` | - |
| `className?` | `string` | - |

***

### Interface: ConnectFilterTabsOption\<TValue\> [#interface-connectfiltertabsoption]

#### Type Parameters

| Type Parameter |
| ------ |
| `TValue` *extends* `string` |

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `value` | `TValue` \| `null` | - |
| `label` | `string` | - |
| `count?` | `number` | Shown as a count beside the label. Omit where a count would need its own query. |

***

### Interface: ConnectFilterTabsProps\<TValue\> [#interface-connectfiltertabsprops]

#### Type Parameters

| Type Parameter |
| ------ |
| `TValue` *extends* `string` |

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `options` | readonly [`ConnectFilterTabsOption`](#interface-connectfiltertabsoption)\<`TValue`\>[] | - |
| `value` | `TValue` \| `null` | - |
| `onChange` | (`value`: `TValue` \| `null`) => `void` | - |
| `className?` | `string` | Merged onto the root, so a utility here replaces the conflicting default rather than sitting beside it. See `cn`. |
| `label` | `string` | Names the group for assistive technology - "Filter contacts by lifecycle". |

***

### Interface: ConnectGrievanceNoticeProps [#interface-connectgrievancenoticeprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `title?` | `ReactNode` | Heading text. `null` renders none, for embedding inside a page that already has one. |
| `applicationName?` | `string` | What this application calls itself in the sentence naming the processor relationship. Omit to leave that sentence out entirely. |
| `className?` | `string` | - |

***

### Interface: ConnectIntegrationsModalProps [#interface-connectintegrationsmodalprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `className?` | `string` | Extra classes on the trigger button. |
| `label?` | `string` | Trigger label, both visible and for screen readers. |

***

### Interface: ConnectLogoProps [#interface-connectlogoprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `name` | `string` | This application's name - rendered as the sub-brand beneath the `cosmo·ops` wordmark, in the house `cosmo·ops VOICE` treatment. |
| `icon?` | `ReactNode` | This application's own glyph, drawn inside the accent badge. Any node - a `lucide-react` icon, an `<img>`, an inline `<svg>`. Paint it with `currentColor` and it inherits `--accent-foreground` from the badge, so it stays legible when a host app changes the accent. Falls back to the Connect rings when omitted. |
| `variant?` | `"mark"` \| `"wordmark"` \| `"full"` | Which halves of the lockup to draw. - `"full"` - badge and wordmark, the default. - `"mark"` - the badge alone, for a collapsed rail or a favicon-sized slot. - `"wordmark"` - the words alone, for a layout that already draws the badge somewhere fixed. The sidebar header uses this: the badge stays put while the words fade, and rendering a second badge only to hide it with a CSS child selector is how that kind of layout quietly rots. |
| ~~`collapsed?`~~ | `boolean` | Mark only, no wordmark. **Deprecated** Pass `variant="mark"`. Kept because it is the published 0.11 API; `variant` wins when both are given. |
| `className?` | `string` | - |

***

### Interface: ConnectNavItem [#interface-connectnavitem]

One destination in the shell's sidebar.

`href` is a path in *your* app, not a Connect URL - the shell never
routes anywhere itself, it hands `href` to `renderLink` and lets your
router do the work.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `label` | `string` | - |
| `href` | `string` | - |
| `icon?` | `ReactNode` | A pre-rendered glyph, e.g. `<LayoutDashboard className="size-5" />`. Required in practice for anything in the primary rail: the collapsed rail is icon-only, and an item with no icon collapses to an empty box. |
| `badge?` | `ReactNode` | Trailing content on the row - a count `<Chip>`, a status dot. Hidden while collapsed. |
| `exact?` | `boolean` | Match this route only when the path is exactly `href`, never as a prefix. `/` is always treated as exact regardless, since every path starts with it. |
| `external?` | `boolean` | Opens in a new tab, with a trailing arrow. For docs, status pages, anything outside the app. |
| `items?` | `ConnectNavItem`[] | One level of children, revealed beneath the parent when the parent or a child is active. |
| `gateExempt?` | `boolean` | Leave this route *outside* `ConnectAccessGate` - see `AppShellProps.gate`. Declared here, next to the route it applies to, rather than as a second list somewhere else that has to be kept in step with this one. |
| `audience?` | `"member"` \| `"staff"` | Who this route is for. Omit - the default - and it renders for everyone, which is what every route on an ordinary application is. - `"staff"` shows it only to a Connect platform staff session; your operator console goes here. - `"member"` hides it *from* staff. Reach for it on anything that reads the workspace - billing, plans, usage, team - since a staff session has no workspace and those routes can only fail for it. Filtering here is presentation, not access: it decides which links a person sees, and a link is not a permission. The route itself must still check `staff` on your server - see `<ConnectPlatformGate>`. |

***

### Interface: ConnectNavLinkProps [#interface-connectnavlinkprops]

Props the shell hands to `renderLink`. Spread them onto whatever your router's link component is.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `href` | `string` | - |
| `className` | `string` | - |
| `children` | `ReactNode` | - |
| `onClick?` | () => `void` | - |
| `aria-current?` | `"page"` | - |
| `aria-label` | `string` | Always set, and the only name the link has while the rail is collapsed - spread it through rather than dropping it, or an icon-only rail reads as a column of unlabelled links. |
| `target?` | `string` | - |
| `rel?` | `string` | - |

***

### Interface: ConnectNavSection [#interface-connectnavsection]

A titled group of items. Untitled groups render as a plain divider-free block.

#### Properties

| Property | Type |
| ------ | ------ |
| `title?` | `string` |
| `items` | [`ConnectNavItem`](#interface-connectnavitem)[] |

***

### Interface: ConnectNoSubscriptionCardProps [#interface-connectnosubscriptioncardprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `plansHref?` | `string` | Where your plan catalogue is mounted. Defaults to `/plans`. |
| `title?` | `string` | Override the headline - "Upgrade to unlock reports", say, rather than the generic one. |
| `description?` | `string` | - |
| `className?` | `string` | - |

***

### Interface: ConnectOneTapHint [#interface-connectonetaphint]

What Connect says about the visitor, before anything has been signed.

Note what is *not* here when `consented` is `false`: no name, no email, no
photo. Connect withholds all of it until the visitor has authorised this
application at least once, so the first prompt an application ever shows is
necessarily the impersonal one.

#### Properties

| Property | Type |
| ------ | ------ |
| `status` | `"signed_out"` \| `"signed_in"` |
| `consented?` | `boolean` |
| `user?` | \{ `displayName`: `string` \| `null`; `email`: `string` \| `null`; `photoUrl`: `string` \| `null`; \} |
| `user.displayName` | `string` \| `null` |
| `user.email` | `string` \| `null` |
| `user.photoUrl` | `string` \| `null` |
| `workspace?` | \| \{ `name`: `string`; \} \| `null` |
| `application?` | \{ `name`: `string`; `iconUrl`: `string` \| `null`; \} |
| `application.name` | `string` |
| `application.iconUrl` | `string` \| `null` |

***

### Interface: ConnectOneTapProps [#interface-connectonetapprops]

"Continue as …" - a signed-out visitor who is already signed into Connect is
offered their account, and one tap signs them in **without leaving the page**.

The prompt is the visible half; what makes it worth having is that finding
out costs one `fetch` rather than a redirect. `ConnectProviderProps.silentSignIn`
answers the same question by bouncing the browser to Connect and back, which
means a visible full-page navigation on the first load of every signed-out
visit, nothing shown to the visitor while it happens, and no way to say no.

```tsx
<ConnectProvider>
  <App />
  <ConnectOneTap />
</ConnectProvider>
```

##### It only appears when it can actually help

Nothing renders unless the visitor is signed out here, signed in to Connect,
and has not recently dismissed the prompt or signed out of this application.
A visitor with no Connect session sees nothing at all, ever - which is why
this is safe to mount unconditionally, unlike `silentSignIn`, whose cost
lands on exactly the people it cannot help.

##### Two prompts, because consent is not assumed

A visitor who has authorised this application before gets their name, email,
workspace and photo, because Connect will release those to an application
they have already agreed to. A visitor who has not gets an impersonal prompt
and a tap that leads to the real consent screen - Connect does not hand over
so much as a first name until that has happened once.

##### Placement

Bottom of the viewport on a phone, bottom-right from `sm:` up. Deliberately
not top-right: on a phone that is where the browser chrome and the
application's own header live, and this must never cover the thing the
visitor came to use.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `onInteractiveSignIn?` | () => `void` | Where to send someone whose tap needs the interactive screen - the first connection to this application, or an account that has an MFA or subscription step outstanding. Defaults to the bridge's own `/signin`. |
| `className?` | `string` | - |

***

### Interface: ConnectPlansPageProps [#interface-connectplanspageprops]

#### Extends

- [`ConnectPlansProps`](#interface-connectplansprops)

#### Properties

| Property | Type | Description | Inherited from |
| ------ | ------ | ------ | ------ |
| `title?` | `string` | - | - |
| `description?` | `string` | - | - |
| `showCurrentSubscription?` | `boolean` | Show the current subscription above the grid. On by default - someone arriving at a pricing page while already subscribed is usually here to change something, and the first thing they need is what they are on now. | - |
| `planIds?` | `string`[] | Restrict the grid to these plan ids, in this order. Defaults to every active plan. | [`ConnectPlansProps`](#interface-connectplansprops).[`planIds`](#interface-connectplansprops) |
| `hideCoupon?` | `boolean` | Hide the coupon field. | [`ConnectPlansProps`](#interface-connectplansprops).[`hideCoupon`](#interface-connectplansprops) |
| `className?` | `string` | - | [`ConnectPlansProps`](#interface-connectplansprops).[`className`](#interface-connectplansprops) |

***

### Interface: ConnectPlansProps [#interface-connectplansprops]

#### Extended by

- [`ConnectPlansPageProps`](#interface-connectplanspageprops)

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `planIds?` | `string`[] | Restrict the grid to these plan ids, in this order. Defaults to every active plan. |
| `hideCoupon?` | `boolean` | Hide the coupon field. |
| `className?` | `string` | - |

***

### Interface: ConnectPlatformGateProps [#interface-connectplatformgateprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `roles?` | `string`[] | Restrict to these platform roles. Omit to accept any staff session, which is the common case - your application already chose which roles it accepts when a platform admin turned staff access on for it, and repeating that list here is a second copy to keep in sync. Pass one only when your own console has tiers: a `developer` seeing the read-only half of what a `platform_admin` can edit. |
| `children` | `ReactNode` | - |
| `fallback?` | `ReactNode` | Rendered for an ordinary member session. Defaults to `null` - hiding the subtree, which is what this gate is normally for. An operator console is usually better hidden than explained. |
| `loading?` | `ReactNode` | Shown until the session read settles. Defaults to nothing. |

***

### Interface: ConnectProfileModalProps [#interface-connectprofilemodalprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `isOpen` | `boolean` | Controlled open state. |
| `onOpenChange` | (`isOpen`: `boolean`) => `void` | - |
| `onSignOut?` | () => `void` | Called after sign-out completes, e.g. to route home. |
| `theme?` | [`ThemeMode`](/docs/reference/core#type-alias-thememode) | See `ConnectUserButtonProps.theme`. |
| `onThemeChange?` | (`theme`: [`ThemeMode`](/docs/reference/core#type-alias-thememode)) => `void` | - |

***

### Interface: ConnectProviderProps [#interface-connectproviderprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `clientId?` | `string` | This application's public `client_id`, from its Connect client section in Connect. Not needed for the default sign-in flow: `signIn()` with no `redirectUri` bounces the browser to your own `/signin` route, which already knows `client_id` server-side (`CONNECT_CLIENT_ID`) and builds the authorize URL itself - the browser never needs it. Pass this only alongside an explicit `redirectUri`, when you're building that URL yourself instead. |
| `basePath?` | `string` | Where `createConnectHandler` is mounted in your app. Must match its `basePath`. |
| `redirectUri?` | `string` | Where Connect sends the user back after sign-in. Your own callback route, not a Connect URL - it is your backend that exchanges the code. |
| `appUrl?` | `string` | Override for non-production Connect instances. Defaults to `https://connect.cosmoops.com`. The twin of `CONNECT_APP_URL`, and *not* derived from it - that variable is read on your server and never reaches the browser. This value is where the user menu's Profile, Billing and Invoices destinations open, where `ConnectAppsGrid` sends an unsubscribed application, where checkout opens, and - if you pass `redirectUri` and so build the authorize URL here rather than in your own `/signin` route - where sign-in goes. Point the server somewhere else and leave this alone and the two halves disagree, so pass both or neither. Your root layout is a Server Component, so `appUrl={process.env.CONNECT_APP_URL}` is enough and needs no browser-visible variable. |
| `fetch?` | (`input`: `string`, `init?`: `RequestInit`) => `Promise`\<`Response`\> | Replace the `fetch` used to reach your bridge. The browser-side twin of `ConnectClientOptions.fetch` - it exists so Storybook and tests can drive the real provider against canned responses instead of monkey-patching `window.fetch`, which is a render-time global mutation and races with child effects. |
| `silentSignIn?` | `boolean` | Sign in a returning visitor automatically, with no click. When the first `/session` read comes back signed out, the browser is sent on one round trip to Connect with OIDC `prompt=none`. If they still have a Connect session and have authorised this app before, they come straight back signed in and never see a screen. If not, they come straight back signed out, with no error and nothing rendered differently - your normal signed-out UI just appears a beat later. **Off by default, and it is a real trade, not just caution.** It costs a full-page redirect on the first load of every signed-out visit - a brief flash on a marketing page, invisible on an app shell - so it belongs on apps whose visitors are overwhelmingly already Connect users. It is attempted at most once per tab (a `sessionStorage` marker), so a visitor who genuinely has no session does not bounce on every navigation. Requires `createConnectHandler` mounted at `basePath`; the redirect goes to your own `/signin`, which mints the `state` server-side. |
| `handoff?` | [`ConnectHandoffMode`](#type-alias-connecthandoffmode) | How every Connect button in this package - Billing, Profile, Manage, checkout, a readiness blocker's fix - takes the visitor to Connect. - `"popup"` (the default): Connect opens in a new tab and **closes itself** when the task is done, telling this page first, so the visitor is back where they were with everything repainted. A blocked popup falls back to `"redirect"` rather than leaving them with nothing. - `"redirect"`: this tab goes to Connect and comes back. Prefer it for an installed PWA or an app inside an iframe, where a second tab is a different window the visitor may never find. Both need this app's origin registered among the client's redirect URIs in Connect - an unregistered origin still reaches Connect, it just gets no way back. |
| `onHandoffResult?` | (`result`: [`ConnectHandoffResult`](#type-alias-connecthandoffresult)) => `void` | Told how each trip to Connect ended - from the popup's message, or from `?connect_result=` on the way back from a redirect (which the provider then strips from the address bar). The provider already re-reads the session and every Connect resource; this is for your own state. |
| `children` | `ReactNode` | - |

***

### Interface: ConnectReadinessBannerProps [#interface-connectreadinessbannerprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `includeAdvisory?` | `boolean` | Show advisory blockers too. Off by default - they are not a reason to interrupt anyone mid-task. |
| `hideWhenReady?` | `boolean` | Render nothing at all once the workspace is ready. On by default. |
| `className?` | `string` | - |

***

### Interface: ConnectReadinessGateProps [#interface-connectreadinessgateprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `children` | `ReactNode` | - |
| `fallback?` | `ReactNode` | Rendered instead of `children` while blocked. Defaults to the banner in a card. |
| `loading?` | `ReactNode` | Shown while the first readiness read is in flight. |
| `className?` | `string` | - |

***

### Interface: ConnectSession [#interface-connectsession]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `user` | [`ConnectUser`](/docs/reference/core#interface-connectuser) \| `null` | - |
| `subscription` | \| [`SessionSubscription`](/docs/reference/core#interface-sessionsubscription) \| `null` | - |
| `role` | `string` \| `null` | The signed-in account's role on this application, or `null` - see `SessionAccess.role`. `null` when signed out too, which is why `user` rather than this is the signed-in test. |
| `permissions` | `string`[] | What to gate on - see `SessionAccess.permissions`. `[]` when signed out. |
| `staff` | `boolean` | Whether this is one of Connect's own operators - see `SessionAccess.staff`. `false` when signed out. |
| `audience` | [`SessionAudience`](/docs/reference/core#interface-sessionaudience) \| `null` | `null` when signed out, for the same reason `user` and `subscription` are: there is no workspace to compare an audience against yet. Never `null` on a signed-in session. |
| `oneTap?` | \| \{ `hintUrl`: `string`; `authorizeUrl`: `string`; \} \| `null` | Where the browser may ask Connect about the visitor, or `null` when this deployment cannot run One Tap - see `IdentityResource.oneTapEndpoints`. Delivered on the session read the provider already makes, so discovering One Tap costs no extra request. Optional because an older bridge will not send it. |
| `clientId?` | `string` \| `null` | This application's public `client_id`, from the bridge - so `openConnect` can name the application on a hand-off without `clientId` being passed to `<ConnectProvider>` as well. Optional because an older bridge will not send it; the provider's own `clientId` prop wins when both exist. |

***

### Interface: ConnectShellContextValue [#interface-connectshellcontextvalue]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `collapsed` | `boolean` | Icon-only rail. Always `false` inside the mobile drawer - see `AppShell`. |
| `collapsible` | `boolean` | Whether a collapse control should be offered at all. |
| `setCollapsed` | (`collapsed`: `boolean`) => `void` | - |
| `renderLink` | [`ConnectLinkRenderer`](#type-alias-connectlinkrenderer) | - |
| `activePath?` | `string` | The host router's current path, if it supplied one. |
| `onNavigate?` | () => `void` | Called after any sidebar link is pressed - the mobile drawer closes itself with this. |

***

### Interface: ConnectSidebarHeaderProps [#interface-connectsidebarheaderprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `name` | `string` | This application's name, in the `cosmo·ops VOICE` lockup. See `ConnectLogo`. |
| `icon?` | `ReactNode` | This application's glyph, drawn on the CosmoOps badge. |
| `homeHref?` | `string` | Where the lockup links when the rail is expanded. Defaults to `/`. |

***

### Interface: ConnectSidebarNavProps [#interface-connectsidebarnavprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `nav` | [`ConnectNav`](#type-alias-connectnav) | Items, sections, or a mix - see `ConnectNav`. |
| `activePath?` | `string` | The current path. Pass your router's own (`usePathname()` in Next). Inherited from `AppShell` when omitted; without either, the nav reads `window.location` and patches `history` to hear about client-side navigations - which works, but is guesswork the router never has to do. See `useActivePath`. |
| `renderLink?` | [`ConnectLinkRenderer`](#type-alias-connectlinkrenderer) | See `ConnectLinkRenderer`. Inherited from the shell; defaults to a plain `<a>`. |
| `collapsed?` | `boolean` | Icon-only rail. Inherited from the shell when omitted. |
| `onNavigate?` | () => `void` | Called after any nav link is pressed - the mobile drawer closes itself with this. |
| `className?` | `string` | - |

***

### Interface: ConnectSidebarProps [#interface-connectsidebarprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `children` | `ReactNode` | The rail's contents, top to bottom. `AppShell` composes the default set; pass your own to rearrange them. |
| `label?` | `string` | Accessible name for the `<aside>`. Defaults to `"Sidebar"`. |
| `className?` | `string` | - |

***

### Interface: ConnectSubscribeDialogProps [#interface-connectsubscribedialogprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `plan` | [`Plan`](/docs/reference/core#interface-plan) \| `null` | `null` closes it. The plan being subscribed to or moved to. |
| `action` | [`PlanAction`](#type-alias-planaction) | Which of subscribe / upgrade / downgrade this is - decides the headline and what the notice says about money. |
| `current` | \| [`SubscriptionResponse`](/docs/reference/core#interface-subscriptionresponse) \| `null` | The workspace's live subscription, for the "from" side of a plan change. |
| `coupon?` | `string` | Coupon typed into the plan grid, echoed here so it is visible before the hand-off carries it. |

#### Methods

##### onClose()

```ts
onClose(): void;
```

Whether this workspace's `plan.trialDays` still applies is Connect's call, not this component's - see the notice below.

###### Returns

`void`

***

##### onConfirm()

```ts
onConfirm(): void;
```

###### Returns

`void`

***

### Interface: ConnectSubscriptionCardProps [#interface-connectsubscriptioncardprops]

#### Properties

| Property | Type |
| ------ | ------ |
| `plansHref?` | `string` |
| `className?` | `string` |

***

### Interface: ConnectSubscriptionGateProps [#interface-connectsubscriptiongateprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `children` | `ReactNode` | - |
| `plansHref?` | `string` | Where your plan catalogue is mounted. Defaults to `/plans`. |
| `fallback?` | `ReactNode` | Replaces the default "not on a plan yet" card. |
| `loading?` | `ReactNode` | Shown while the first read is in flight. Defaults to a skeleton, never a fallback - see below. |
| `allowTrialing?` | `boolean` | Let a trialing subscription through. On by default: a trial is a subscription, and gating it out is how a customer gets sold a trial and then locked out of it. |
| `allowPending?` | `boolean` | Let a subscription whose first debit hasn't confirmed through. On by default - the charge settles on a webhook and can take minutes, and the alternative is telling someone who just paid that they haven't. |
| `className?` | `string` | - |

***

### Interface: ConnectTrialBannerProps [#interface-connecttrialbannerprops]

#### Properties

| Property | Type |
| ------ | ------ |
| `plansHref?` | `string` |
| `className?` | `string` |

***

### Interface: ConnectUsageMeterProps [#interface-connectusagemeterprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `addonIds?` | `string`[] | Restrict to these add-on ids, in this order. Defaults to every metered add-on. |
| `hideWhenEmpty?` | `boolean` | Render nothing when there is nothing to meter, instead of an empty-state card. On by default. |
| `title?` | `string` | - |
| `className?` | `string` | - |

***

### Interface: ConnectUserButtonProps [#interface-connectuserbuttonprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `variant?` | `"inline"` \| `"row"` \| `"compact"` | How the trigger is drawn. - `"row"` - a full-width sidebar row: avatar, name over email, and a trailing glyph. The default, because the account control now lives in the rail's footer rather than in a top bar. - `"compact"` - the avatar alone, for a dense strip. - `"inline"` - avatar and name side by side, for a bar with room. |
| `onSignOut?` | () => `void` | Called after sign-out completes, e.g. to route home. |
| `className?` | `string` | - |
| `theme?` | [`ThemeMode`](/docs/reference/core#type-alias-thememode) | Current theme, and where to send a change - the SDK has no theme provider of its own (no `next-themes` dependency), so this is entirely host-controlled. The Appearance section only renders when both are supplied; omit either to leave theme control out of the modal. |
| `onThemeChange?` | (`theme`: [`ThemeMode`](/docs/reference/core#type-alias-thememode)) => `void` | - |

***

### Interface: ConnectUserMenuProps [#interface-connectusermenuprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `compact?` | `boolean` | Hide the name beside the avatar - for a dense top bar. |
| `onSignOut?` | () => `void` | Called after sign-out completes, e.g. to route home. |
| `className?` | `string` | - |
| `theme?` | [`ThemeMode`](/docs/reference/core#type-alias-thememode) | Current theme, and where to send a change - the SDK has no theme provider of its own (no `next-themes` dependency), so this is entirely host-controlled. The Appearance section only renders when both are supplied; omit either to leave theme control out of the modal. |
| `onThemeChange?` | (`theme`: [`ThemeMode`](/docs/reference/core#type-alias-thememode)) => `void` | - |

***

### Interface: ConnectVisitorChatProps [#interface-connectvisitorchatprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `workspaceId` | `string` | The customer whose website this widget is on. Required, and it is the one field that decides whose published knowledge the assistant answers from and whose diary it books into. Your bridge checks it against your own subscriptions before it reaches Connect. |
| `assistantName?` | `string` | What the assistant calls itself, and the copy shown before anybody types. **Yours, not Connect's.** These never reach the model and are rendered from your own store, which is what lets the bubble paint its first frame with no network call at all. Connect holds what it enforces; you hold what you draw. |
| `greeting?` | `string` | - |
| `suggestions?` | readonly `string`[] | Opening questions offered as buttons. Three is what fits above a phone keyboard. |
| `bookingEventTypeSlug?` | `string` \| `null` | A published event type slug in that workspace, to offer a call. Omit for no booking. |
| `basePath?` | `string` | Where `createConnectHandler` is mounted. Defaults to `/api/connect`, which is its own default. Passed explicitly here, rather than only inherited from `ConnectProvider`, because this component is built to work on a page that has no provider - see the component's own note. |
| `page?` | `string` | The page the visitor is on, passed to Connect as context. Left unset it uses `location.pathname`. Pass something more meaningful - a page title, a product slug - where the path is not descriptive. |
| `title?` | `string` | The bubble's accessible label, and the panel's heading. |
| `defaultOpen?` | `boolean` | Start open. The panel owns its state after that. |
| `showWhenSignedIn?` | `boolean` | Show the bubble even to a signed-in user. Off by default. The default is the useful one: a signed-in member has your product's own support surfaces and, if you mount it, `<ConnectAskAi>` - which is grounded in their actual workspace and can therefore answer things this assistant cannot. Two bubbles in the same corner is also the one layout bug this component could cause, since `<ConnectAskAi>` sits at the same `z-30` bottom-right position. Turn it on for a marketing site that is a separate surface from the application, where "signed in" says nothing about what the visitor is reading. |
| `fetch?` | (`input`: `string`, `init?`: `RequestInit`) => `Promise`\<`Response`\> | Replaces `fetch`, for a mount with **no `ConnectProvider`** around it. Inside a provider this is not needed and not read: the component goes through `mutateResource`/`streamResource`, which already use whatever `fetch` that provider was given - so Storybook and the docs site work by wrapping it, with nothing passed here. |

***

### Interface: ConnectWorkspaceSwitcherProps [#interface-connectworkspaceswitcherprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `onSwitched?` | (`workspace`: [`UserWorkspace`](/docs/reference/core#interface-userworkspace)) => `void` | Called after a successful switch. Defaults to a full reload, which is almost always right: every server-rendered page in the host application was built for the *previous* workspace, and a client-side refresh would leave stale data on screen next to the new workspace's name. |
| `row?` | `boolean` | Draw the trigger as a sidebar row rather than a plain button. On by default inside `AppShell`, where this sits directly under the brand; pass `false` for a standalone placement in your own chrome. |
| `className?` | `string` | - |
| `alwaysShow?` | `boolean` | Render even when the signed-in account has fewer than two workspaces. Off by default, which keeps this component's documented posture for every other host: an account with one workspace sees nothing, because a switcher offering one option teaches it nothing. Pass `true` on an application that wants a single-workspace account to be able to discover - and open - a second one without leaving its own chrome. Turning this on adds a "Create workspace" row that opens `<ConnectCreateWorkspaceModal>` inline: name, individual-or-business, and the tenant's own plan decides whether another workspace is allowed at all - the same guards Connect's own "Create workspace" button runs through, surfaced as the server wrote them rather than pre-checked here. |

***

### Interface: ConnectWorkspaceTypeGateProps [#interface-connectworkspacetypegateprops]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `type` | \| [`WorkspaceType`](/docs/reference/core#type-alias-workspacetype) \| [`WorkspaceType`](/docs/reference/core#type-alias-workspacetype)[] | The workspace type this subtree is for. Pass an array to accept either - which is the same as not using the gate at all, so it exists only to keep a `type={someVariable}` call site from having to special-case "both". |
| `children` | `ReactNode` | - |
| `fallback?` | `ReactNode` | Rendered for a workspace of the other type. Defaults to `null` - hiding the subtree, which is what this gate is normally for. |
| `loading?` | `ReactNode` | Shown until the session read settles. Defaults to nothing. |

***

### Interface: GridWindow [#interface-gridwindow]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `startMinute` | `number` | Minutes since local midnight the grid starts at - `480` is 08:00. |
| `endMinute` | `number` | - |

***

### Interface: OpenConnectOptions [#interface-openconnectoptions]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `features?` | `string` | `window.open` features for a sized pop-up window instead of a tab - checkout uses `"popup,width=520,height=780"`. Must not include `noopener`: the window handle is how Connect reports back and closes. |
| `name?` | `string` | `window.open` target name. A second `openConnect` with the same name reuses that window. |

***

### Interface: PlanTier [#interface-plantier]

One tier of the catalog: the product, and whichever cadences it is sold at.

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `planGroup` | `string` | - |
| `name` | `string` | - |
| `description` | `string` | - |
| `monthly` | [`Plan`](/docs/reference/core#interface-plan) \| `null` | - |
| `annual` | [`Plan`](/docs/reference/core#interface-plan) \| `null` | - |
| `oneTime` | [`Plan`](/docs/reference/core#interface-plan) \| `null` | A plan paid once, never on a cadence - its own slot rather than falling into `annual`. A one-time plan has no renewal to discount against, so filing it as "annual" used to let a tier mixing a recurring plan with a one-time one compute a `annualSavingPercent` between a monthly charge and a lump sum, and offered the one-time purchase under the annual toggle as though it renewed. `pickTierPlan` in `plans.tsx` is the only reader that needs to know this slot exists. |
| `annualSavingPercent` | `number` \| `null` | Whole-percent saving from paying annually, or `null` when this tier has no pair to compare. |

***

### Interface: PositionedBooking [#interface-positionedbooking]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `booking` | [`Booking`](/docs/reference/core#interface-booking) | - |
| `dateKey` | `string` | - |
| `top` | `number` | Fraction of the visible day, `0`-`1`, from the top of the grid. |
| `height` | `number` | Fraction of the visible day. Never `0` - see `MIN_VISIBLE_FRACTION`. |
| `column` | `number` | Column index within its overlap cluster, and how many columns that cluster has. |
| `columns` | `number` | - |
| `clipped` | `boolean` | True when the appointment extends past the grid's visible window. |

***

### Interface: SlotQuery [#interface-slotquery]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `eventTypeId` | `string` | - |
| `from` | `Date` | - |
| `to` | `Date` | - |
| `excludeBookingId?` | `string` | Set to the booking being moved. Without it the appointment counts as blocking its own replacement and a reschedule picker comes back empty for the one adjustment people actually make. |

***

### Interface: StartCheckoutOptions [#interface-startcheckoutoptions]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `url` | `string` | Absolute Connect URL to open. Build it with `buildCheckoutUrl`. |

#### Methods

##### isSettled()

```ts
isSettled(entitlements: ConnectEntitlements): boolean;
```

`true` once `/entitlements` reflects the change being waited on.

Compared against the entitlements read *before* the window opened, so a
predicate is genuinely needed - "the subscription exists" is the right
test for a first subscribe and the wrong one for a plan change, where a
subscription existed all along.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `entitlements` | [`ConnectEntitlements`](#interface-connectentitlements) |

###### Returns

`boolean`

***

### Interface: UseConnectPush [#interface-useconnectpush]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `status` | [`ConnectPushStatus`](#type-alias-connectpushstatus) | - |
| `isEnabling` | `boolean` | True while `enable()` is in flight. |
| `error` | `string` \| `null` | The last failure, in words a person can act on. Cleared by the next attempt. |
| `enable` | () => `Promise`\<`string` \| `null`\> | Ask for permission, register this browser with Connect, and resolve the device id - or `null` if the member declined or anything refused. Must be called from a user gesture: browsers refuse `Notification.requestPermission()` outside one, and the refusal is indistinguishable from the member saying no. |
| `disable` | (`deviceId`: `string`) => `Promise`\<`void`\> | Stop notifications on one browser, by the id `enable()` returned. |

***

### Interface: UseConnectResourceOptions [#interface-useconnectresourceoptions]

Options for [useConnectResource](#function-useconnectresource).

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `requiresSession?` | `boolean` | Whether the read has to wait for a session. `true` by default, and right for every route but one. Pass `false` only for a bridge route that takes neither a session nor a workspace - today that is `GET /company-profile` alone, and its handler says why. Passing it for anything else does not widen access (the bridge still refuses), it just buys a guaranteed 401 sooner. |

***

### Interface: VisitorStreamHandlers [#interface-visitorstreamhandlers]

The visitor turn's wire format, read.

**A named-event reader, where `<ConnectAskAi>`'s is data-only.** That one
consumes the AI SDK's UI message stream, where every frame is a `data:` line
carrying a `type` field. This endpoint answers `event:`/`data:` pairs instead,
for the reason the route explains: the UI message protocol exists for
`useChat`, which owns the message list client-side, and this surface
deliberately does not let the client own the transcript.

Kept out of the component and pure so it can be tested without a DOM. The
three behaviours worth pinning are all here: a frame split across two network
reads, a malformed frame, and a stream that ends without its `done`.

#### Methods

##### onDelta()

```ts
onDelta(text: string): void;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `text` | `string` |

###### Returns

`void`

***

##### onDone()

```ts
onDone(done: VisitorTurnDone): void;
```

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `done` | [`VisitorTurnDone`](/docs/reference/core#interface-visitorturndone) |

###### Returns

`void`

***

##### onError()

```ts
onError(message: string): void;
```

A failure the server reported *after* the stream opened.

It arrives as an event rather than a status because the status was already
sent, and the message is written as a sentence for the same reason - it is
shown to the visitor verbatim.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `message` | `string` |

###### Returns

`void`

***

### Interface: WorkspaceActionsState [#interface-workspaceactionsstate]

#### Properties

| Property | Type | Description |
| ------ | ------ | ------ |
| `isPending` | `boolean` | - |
| `error` | `string` \| `null` | The last refusal, written to be shown to the person who clicked - see `CreateWorkspaceUseCase`. |

#### Methods

##### clearError()

```ts
clearError(): void;
```

###### Returns

`void`
## Type Aliases

### Type Alias: AiStatusTone [#type-alias-aistatustone]

```ts
type AiStatusTone = "neutral" | "positive" | "warning" | "danger";
```

***

### Type Alias: ConnectAccessGateState [#type-alias-connectaccessgatestate]

```ts
type ConnectAccessGateState = 
  | "loading"
  | "signed_out"
  | "suspended"
  | "audience_mismatch"
  | "no_subscription"
  | "onboarding_pending"
  | "blocked"
  | "payment_retry"
  | "clear";
```

***

### Type Alias: ConnectCheckoutStatus [#type-alias-connectcheckoutstatus]

```ts
type ConnectCheckoutStatus = "idle" | "waiting" | "completed" | "abandoned" | "blocked";
```

Where a hand-off to Connect has got to.

- `idle` - nothing started, or the last one was acknowledged.
- `waiting` - Connect is open and this hook is polling `/entitlements`.
- `completed` - the entitlement actually changed. The only success signal
  worth trusting; see this module's doc comment for why the Connect window
  closing is not one.
- `abandoned` - the window closed (or the deadline passed) with the
  entitlement unchanged.
- `blocked` - **no longer produced.** A blocked window now falls back to a
  same-tab trip to Connect and back (see `openConnect`), which reaches the
  same place without asking the customer to click a fallback link. Kept in
  the union so code that handles it still compiles.

***

### Type Alias: ConnectHandoffMode [#type-alias-connecthandoffmode]

```ts
type ConnectHandoffMode = "popup" | "redirect";
```

How this application sends people to Connect - see `ConnectProviderProps.handoff`.

***

### Type Alias: ConnectHandoffResult [#type-alias-connecthandoffresult]

```ts
type ConnectHandoffResult = "completed" | "cancelled" | "closed";
```

How a trip to Connect ended, as Connect reports it.

Advisory. A payment confirmed by webhook can settle a moment after the
window has closed, so `"completed"` means "they finished there", not "it is
live" - re-read entitlements for that. The provider already does, for every
hand-off.

***

### Type Alias: ConnectLinkButtonProps [#type-alias-connectlinkbuttonprops]

```ts
type ConnectLinkButtonProps = Omit<ButtonProps, "render"> & {
  href: string;
  renderLink?: (props: ConnectLinkButtonRenderProps) => ReactElement;
};
```

#### Type Declaration

##### href

```ts
href: string;
```

##### renderLink?

```ts
optional renderLink?: (props: ConnectLinkButtonRenderProps) => ReactElement;
```

The host's router link, if it has one.

Without it this renders a plain `<a href>`, which navigates under every
router but costs a **full document load** - in an App Router app that
throws away the client cache and re-runs the whole tree, which is very
visible on a button sitting inside a page the user is working in.

```tsx
<ConnectLinkButton href="/deals" renderLink={(props) => <Link {...props} />}>
  Open board
</ConnectLinkButton>
```

Spread the props through whole - see `ConnectLinkButtonRenderProps`.
Same shape as `AppShell`'s `renderLink`, so a host declares its router
once and passes the same function to both. This package imports nothing
from `next` - see the `./next` entry's doc comment for why that is a hard
rule - so handing the link component in is the only way it can be a client
transition.

###### Parameters

| Parameter | Type |
| ------ | ------ |
| `props` | [`ConnectLinkButtonRenderProps`](#type-alias-connectlinkbuttonrenderprops) |

###### Returns

`ReactElement`

***

### Type Alias: ConnectLinkButtonRenderProps [#type-alias-connectlinkbuttonrenderprops]

```ts
type ConnectLinkButtonRenderProps = Omit<ComponentPropsWithRef<"a">, "href" | "children"> & {
  href: string;
  children: ReactNode;
};
```

What `ConnectLinkButton` hands its `renderLink`: everything a React Aria
`Button` puts on its element, already merged - the `ref`, the press,
keyboard, focus and hover handlers, `id`, the `aria-*` and `data-*`
attributes HeroUI styles from, `className` and `children` - retargeted at an
anchor. **Spread all of it** onto your link component; picking out `href`,
`className` and `children` is exactly what disconnects the ref and the press
handling.

#### Type Declaration

##### href

```ts
href: string;
```

##### children

```ts
children: ReactNode;
```

***

### Type Alias: ConnectLinkRenderer [#type-alias-connectlinkrenderer]

```ts
type ConnectLinkRenderer = (props: ConnectNavLinkProps) => ReactNode;
```

How this app renders a link.

The SDK ships into any React app and cannot import `next/link`,
`react-router` or anything else framework-specific, so routing is a prop.
The default is a plain `<a>`, which is correct but costs a full page load;
an app with a client router should pass its own:

```tsx
renderLink={(props) => <Link {...props} />}   // next/link
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `props` | [`ConnectNavLinkProps`](#interface-connectnavlinkprops) |

#### Returns

`ReactNode`

***

### Type Alias: ConnectNav [#type-alias-connectnav]

```ts
type ConnectNav = ReadonlyArray<
  | ConnectNavItem
| ConnectNavSection>;
```

What `AppShell`/`ConnectSideNav` accept for `nav`: a flat list of
items, a list of sections, or a mix. Consecutive bare items fold into one
untitled section, so the common case is just an array of items.

***

### Type Alias: ConnectOneTapFailure [#type-alias-connectonetapfailure]

```ts
type ConnectOneTapFailure = 
  | "unavailable"
  | "login_required"
  | "consent_required"
  | "interaction_required"
  | "rate_limited"
  | "failed";
```

Why a tap did not end in a session.

Every one of these is ordinary traffic rather than a fault, which is why
`oneTapSignIn` reports them as a value instead of throwing: `consent_required`
and `interaction_required` both mean "this needs the interactive screen", and
that is a hand-off, not an error to show anybody.

***

### Type Alias: ConnectPushStatus [#type-alias-connectpushstatus]

```ts
type ConnectPushStatus = 
  | "checking"
  | "unsupported"
  | "unconfigured"
  | "denied"
  | "prompt"
  | "granted";
```

Whether this browser can show notifications, and whether it has been asked.

Five states, not a boolean - and that is the whole reason this hook exists
rather than a `<Switch>`:

- `unsupported` - no service worker or no Push API. On iOS this is every
  browser until the site is added to the home screen, which is worth saying
  out loud because it is most of the phones in some markets.
- `unconfigured` - the platform has no VAPID keys, so nothing could be
  delivered even if permission were granted.
- `denied` - **and this one cannot be undone by script.** A browser will not
  show the prompt again once told no; only the site-settings panel in the
  address bar can reverse it. A control that silently does nothing when
  pressed is the worst version of this screen, so the state is surfaced and
  the UI is expected to explain it.
- `prompt` - askable.
- `granted` - permission held. It does **not** follow that this browser is
  registered with Connect; permission and registration are separate facts,
  and a member who cleared site data holds the first without the second.

***

### Type Alias: ConnectSidebarRowProps [#type-alias-connectsidebarrowprops]

```ts
type ConnectSidebarRowProps = Readonly<{
  icon: ReactNode;
  label: ReactNode;
  aria-label?: string;
  tile?: boolean;
  trailing?: ReactNode;
}> & Omit<ComponentPropsWithoutRef<"button">, "aria-label">;
```

***

### ~~Type Alias: ConnectSideNavProps~~ [#type-alias-connectsidenavprops]

```ts
type ConnectSideNavProps = ConnectSidebarNavProps;
```

#### Deprecated

Renamed to `ConnectSidebarNavProps`.

***

### Type Alias: ConnectSubscriptionState [#type-alias-connectsubscriptionstate]

```ts
type ConnectSubscriptionState = "none" | "trialing" | "active" | "cancelling" | "pending" | "problem";
```

What a subscription is actually doing, collapsed to the five cases worth
rendering differently.

`status` alone cannot answer this. `"active"` covers both a paying customer
and one still in a trial (`isTrialing` on the session block is what splits
them), and it says nothing about a subscription winding down -
`cancelAtPeriodEnd` is a separate field precisely so a cancelling
subscription is not mistaken for a renewing one.

***

### Type Alias: PlanAction [#type-alias-planaction]

```ts
type PlanAction = 
  | "current"
  | "upgrade"
  | "downgrade"
  | "switch-to-annual"
  | "switch-to-monthly"
  | "subscribe";
```

What moving to `plan` would be, given what the workspace is on today.

`switch-to-annual`/`switch-to-monthly` exist because a change of *cadence*
is neither an upgrade nor a downgrade, and calling it one is not a wording
problem. Connect picks the use-case that runs from the same classification
(`classifyPlanChange` in `packages/data`), and a same-tier move to annual
compares *cheaper* per month - so it used to be sent down the downgrade
path, where the price delta across cadences is negative, floors to zero,
and settles the whole switch for no money at all.
## Variables

### Variable: ACTION\_LABEL [#variable-action_label]

```ts
const ACTION_LABEL: Record<PlanAction, string>;
```

***

### Variable: AI\_EMPTY\_VALUE [#variable-ai_empty_value]

```ts
const AI_EMPTY_VALUE: "—" = "—";
```

Renders in place of a missing value - an em dash, not the word "null".

***

### Variable: AI\_TONE\_CHIP [#variable-ai_tone_chip]

```ts
const AI_TONE_CHIP: Record<AiStatusTone, string>;
```

HeroUI semantic tokens only, so these inherit whatever theme the host defined.

***

### Variable: AI\_TONE\_DOT [#variable-ai_tone_dot]

```ts
const AI_TONE_DOT: Record<AiStatusTone, string>;
```

***

### Variable: CONNECT\_APP\_URL [#variable-connect_app_url]

```ts
const CONNECT_APP_URL: "https://connect.cosmoops.com" = "https://connect.cosmoops.com";
```

Browser half of `@cosmoops/connect-sdk` - React components and hooks.

Holds no credential. Everything here talks to your own origin, where
`createConnectHandler` from `@cosmoops/connect-sdk/server` holds the API key
and reaches Connect.

Styling is HeroUI v3 semantic tokens only (`accent`, `muted`, `surface`,
`separator`, ...), never a hardcoded colour - so these components inherit
whatever theme the host application platform already configured, and one theme
change lands identically in all of them.

***

### Variable: CONNECT\_HANDOFF\_MESSAGE\_TYPE [#variable-connect_handoff_message_type]

```ts
const CONNECT_HANDOFF_MESSAGE_TYPE: "connect:handoff" = "connect:handoff";
```

`MessageEvent.data.type` of the message Connect posts before closing a popup.

***

### Variable: CONNECT\_HANDOFF\_RESULT\_PARAM [#variable-connect_handoff_result_param]

```ts
const CONNECT_HANDOFF_RESULT_PARAM: "connect_result" = "connect_result";
```

The query param Connect adds when it redirects back.

***

### ~~Variable: ConnectSideNav~~ [#variable-connectsidenav]

```ts
const ConnectSideNav: (__namedParameters: ConnectSidebarNavProps) => Element = ConnectSidebarNav;
```

The sidebar's nav column on its own - sections, items, one level of
children, and the active highlight.

`AppShell` renders this inside its rail and its mobile drawer, and it
is exported for a host that wants the shell's navigation without its chrome:
the same items, the same active resolution, dropped into a layout you
already have.

Every prop is optional except `nav`, because inside a shell they are all
inherited from it. Passing one wins over what the shell says - which is how
a second nav column (see `secondaryNav`) can share the rail's collapse state
while keeping its own items.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectSidebarNavProps`](#interface-connectsidebarnavprops) |

#### Returns

`Element`

#### Deprecated

Renamed to `ConnectSidebarNav` in 0.12 - the rail is a
`ConnectSidebar` now, and its pieces are named after it. Identical
behaviour; this alias will be removed in a later minor.

***

### Variable: RAIL\_COLLAPSED [#variable-rail_collapsed]

```ts
const RAIL_COLLAPSED: 64 = 64;
```

The rail's two widths, in pixels, and the one place they are written down.

They are numbers rather than Tailwind classes because the rail animates
between them - `motion` interpolates a number and cannot interpolate
`w-16` into `w-64`.

**The pairing is load-bearing, not arbitrary.** Every row in the sidebar is
laid out so its glyph sits exactly `RAIL_GUTTER + ROW_GUTTER + 10px` from
the rail's left edge - 32px, which is also `RAIL_COLLAPSED / 2`. So the
collapsed rail centres every icon *without a single centring rule*, and
nothing moves horizontally while the width animates. Change either width
and that identity breaks: the icons drift sideways during the transition,
which is the exact stutter this geometry exists to avoid.

***

### Variable: RAIL\_EXPANDED [#variable-rail_expanded]

```ts
const RAIL_EXPANDED: 264 = 264;
```
## Functions

### Function: AiChart() [#function-aichart]

```ts
function AiChart(__namedParameters: Readonly<AiChartInput>): Element;
```

`showChart`. Cartesian kinds are drawn as SVG against a fixed viewBox and
scaled by the browser, which is why there is no resize observer here: the
chart is resolution-independent, so "responsive" costs nothing at runtime.

`pie` is deliberately *not* a pie. In a 400px column a pie plus its legend
is smaller than the labels it needs, and reading angles is worse than
reading lengths at any size - so proportions render as a labelled bar list,
which answers the same question and stays legible.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`AiChartInput`](/docs/reference/core#interface-aichartinput)\> |

#### Returns

`Element`

***

### Function: AiDetails() [#function-aidetails]

```ts
function AiDetails(__namedParameters: Readonly<AiDetailsInput>): Element;
```

`showDetails`: one entity's fields, as a definition list rather than a one-row table.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`AiDetailsInput`](/docs/reference/core#interface-aidetailsinput)\> |

#### Returns

`Element`

***

### Function: AiMetrics() [#function-aimetrics]

```ts
function AiMetrics(__namedParameters: Readonly<AiMetricsInput>): Element;
```

`showMetrics`. A delta is coloured by `deltaMeaning`, not by its sign -
churn rising is bad and revenue rising is good, and painting both green
because the arrow points up is exactly the confidently-wrong signal a
dashboard must never send. The arrow still shows direction, so meaning and
direction stay two separate channels.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`AiMetricsInput`](/docs/reference/core#interface-aimetricsinput)\> |

#### Returns

`Element`

***

### Function: AiSources() [#function-aisources]

```ts
function AiSources(__namedParameters: Readonly<AiSourcesInput>): Element;
```

`showSources`: what a grounded answer actually rested on. The point of a
citation is that it can be opened, so a source with a `uri` is a link and one
without is still a named row - never a marker with nothing behind it.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`AiSourcesInput`](/docs/reference/core#interface-aisourcesinput)\> |

#### Returns

`Element`

***

### Function: aiStatusTone() [#function-aistatustone]

```ts
function aiStatusTone(value: AiValue): AiStatusTone;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `value` | [`AiValue`](/docs/reference/core#type-alias-aivalue) |

#### Returns

[`AiStatusTone`](#type-alias-aistatustone)

***

### Function: AiSteps() [#function-aisteps]

```ts
function AiSteps(__namedParameters: Readonly<AiStepsInput & {
  renderDetail?: (markdown: string) => ReactNode;
}>): Element;
```

`showSteps`: an ordered procedure. `renderDetail` is how the panel feeds each step back through its markdown renderer.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`AiStepsInput`](/docs/reference/core#interface-aistepsinput) & \{ `renderDetail?`: (`markdown`: `string`) => `ReactNode`; \}\> |

#### Returns

`Element`

***

### Function: AiTable() [#function-aitable]

```ts
function AiTable(__namedParameters: Readonly<AiTableInput>): Element;
```

`showTable`. Sorting and CSV are what a table is *for* - once an answer is
tabular the next thing anyone wants is to reorder it or take it elsewhere,
and a markdown table can do neither. Both work off the raw values, so
"₹12,500" sorts as 12500 and the export carries numbers a spreadsheet can add.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`AiTableInput`](/docs/reference/core#interface-aitableinput)\> |

#### Returns

`Element`

***

### Function: AiTimeline() [#function-aitimeline]

```ts
function AiTimeline(__namedParameters: Readonly<AiTimelineInput>): Element;
```

`showTimeline`: dated events, oldest first, on a single rail.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`AiTimelineInput`](/docs/reference/core#interface-aitimelineinput)\> |

#### Returns

`Element`

***

### Function: annualSaving() [#function-annualsaving]

```ts
function annualSaving(monthly: Plan | null | undefined, annual: Plan | null | undefined): number | null;
```

Annual is quoted per year on the wire (`billingInterval: "annual"`), so a
side-by-side grid has to normalise before it can compare. Showing the saving
against 12x the monthly plan is the comparison a customer is actually making.

**Call this with a matched pair from one tier.** It used to be handed the
first monthly and the first annual plan in the whole response - two plans
that were the same tier only by the accident of document order, and that
produced one badge for a catalog where each tier discounts differently.
`groupPlansByTier` now does the pairing.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `monthly` | [`Plan`](/docs/reference/core#interface-plan) \| `null` \| `undefined` |
| `annual` | [`Plan`](/docs/reference/core#interface-plan) \| `null` \| `undefined` |

#### Returns

`number` \| `null`

***

### Function: AppShell() [#function-appshell]

```ts
function AppShell(__namedParameters: AppShellProps): Element;
```

The application frame every CosmoOps application platform runs inside: one
rail down the side holding the brand, the workspace, the navigation and the
account, and your pages in the rest.

This is the piece each partner was otherwise writing by hand, and writing
slightly differently - which is how a family of applications stops looking
like one product. Everything that should be identical across platforms
lives here; everything that genuinely differs is a prop:

```tsx
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { AppShell } from "@cosmoops/connect-sdk/client";
import { LayoutDashboard, Settings, Truck } from "lucide-react";

export function Shell({ children }: { children: React.ReactNode }) {
  return (
    <AppShell
      name="Dispatch"
      icon={<Truck />}
      activePath={usePathname()}
      renderLink={(props) => <Link {...props} />}
      nav={[
        { label: "Overview", href: "/", icon: <LayoutDashboard className="size-5" /> },
        { label: "Shipments", href: "/shipments", icon: <Truck className="size-5" /> },
      ]}
      secondaryNav={[
        { label: "Settings", href: "/settings", icon: <Settings className="size-5" /> },
      ]}
    >
      {children}
    </AppShell>
  );
}
```

**There is no top bar.** There used to be one, carrying a page title, the
launcher and the account menu - and it was a second, half-width strip of
chrome saying things the rail was better placed to say. The launcher and
the account row moved into the rail's footer, where they are one press from
wherever the pointer already is; the page title moved into the page, where
`ConnectPageShell` renders it next to that page's own actions and
breadcrumbs. What is left is a single column of chrome and a full-height
content pane, which is what every application this shell is modelled on
settled at.

The one exception is below `md`, where a slim bar carries the drawer
trigger. A phone has no rail to put it in, and a navigation you cannot open
is not a smaller navigation.

**The rail starts collapsed.** `defaultCollapsed` is `true`: the icon rail
is the resting state, and expanding is the deliberate act. That is the right
default because the rail's job is to be *there*, not to be read - a person
learns five glyphs in a day and then wants the 200px back for their data.
A stored preference from the user's last visit always wins over it.

There is no `isSignedIn` branch anywhere in here, and there should be none
in yours: the auth buttons render nothing once a session exists, the account
row renders nothing without one, and the launcher renders nothing when
signed out. The swap happens inside the components.

It renders no provider of its own - mount `ConnectProvider` above it, once,
so a page with a shell, a plan table and a usage meter still makes one
session read.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`AppShellProps`](#interface-appshellprops) |

#### Returns

`Element`

***

### Function: buildCheckoutUrl() [#function-buildcheckouturl]

```ts
function buildCheckoutUrl(__namedParameters: CheckoutUrlOptions): string;
```

The Connect URL that completes a subscribe or a plan change.

Both land on a modal Connect renders over the relevant page (an intercepted
parallel route - `app/@modal/(.)apps/...` and `app/@modal/(.)billing/...`),
so the customer arrives already looking at the confirmation for the plan
they picked here, not at a catalogue they have to find it in again.

The subscribe path is `/apps/...`, not `/applications/...`: the portal
route customers see is `/apps`, and `/applications` is a Console-only
prefix that has never served this page. Building the other one is what
sent every first-time subscriber from a partner's pricing page to a 404.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`CheckoutUrlOptions`](#interface-checkouturloptions) |

#### Returns

`string`

***

### Function: checkoutSubscriptionId() [#function-checkoutsubscriptionid]

```ts
function checkoutSubscriptionId(action: PlanAction, current: 
  | SubscriptionResponse
  | null): string | null;
```

The subscription id a checkout hand-off should carry for `action` - or
`null`, meaning "send them to Connect's *subscribe* screen".

Paired with `resolveAction` deliberately: the button's label and the URL it
opens are one decision, and deriving the URL from the mere *presence* of a
subscription row instead is what sent a card reading "Subscribe" to
Connect's change-plan screen, which answered `notFound()`.

The gap is real rather than hypothetical. `resolveAction` needs the
**current plan** to classify a change and answers `"subscribe"` without
one - and `SubscriptionResponse` carries `plan: null` beside a perfectly
live `subscription` whenever that plan has been deleted from the catalogue
out from under it, which a re-seeded application does. Connect's change-plan
route is stuck in the same place for the same reason: it cannot prorate
against a price it cannot read.

Sending those to `/apps/.../subscribe/...` is a real hand-off, not a guess:
that route re-reads the workspace's live subscriptions server-side and
redirects to the change-plan screen itself when there genuinely is one to
change. Connect decides, so the grid does not have to be sure.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `action` | [`PlanAction`](#type-alias-planaction) |
| `current` | \| [`SubscriptionResponse`](/docs/reference/core#interface-subscriptionresponse) \| `null` |

#### Returns

`string` \| `null`

***

### Function: ConnectAccessGate() [#function-connectaccessgate]

```ts
function ConnectAccessGate(__namedParameters: ConnectAccessGateProps): Element;
```

Wrap your app's content once - typically in your root layout, alongside
(not instead of) your own header/nav - and it renders exactly one of: the
hard-block card for a suspended workspace, one with no plan, or one
waiting on approval; your `children` plus a dismissible payment-due banner
during an automatic dunning retry; or just `children` once everything is
clear.

**Do not wrap your own plans/pricing page with this.** The `no_subscription`
card's button sends someone to Connect's subscribe page directly (see
`GateCard`'s doc comment on why - it used to link to your in-app `/plans`
instead, which is a dead end once this gate hides that very page too), but
a customer can still navigate to your plans page on their own (a nav link,
a bookmark) and must actually see it there. Since the gate is mounted once
around every route, carving out that exception is your router's job, not
a prop on this component: check the current path in your layout/shell and
skip wrapping `children` in `<ConnectAccessGate>` for that one route.

**Signed-out renders `children`, not a fallback** - deliberately different
from `ConnectReadinessGate`/`ConnectSubscriptionGate`, which show nothing
until signed in. None of the four states this component composes is a
property of a signed-out visitor, and a component mounted around your
entire app must not hide a public landing page behind an empty gate. Your
own sign-in flow decides what a signed-out visitor sees.

**While the verdict is unknown, `children` render.** The two reads behind
this gate are client-side round trips that happen on *every* page load, and
this component is mounted around an entire application - so blocking on
them replaced every partner's whole page with one placeholder until both
came back. Anything the host had streamed underneath was discarded: per-
section skeletons never painted, and a page built as a static shell with
suspended regions arrived all at once anyway. That is the common path, paid
by every workspace on every navigation.

Rendering `children` instead makes the loading case agree with the two
cases either side of it. A *failed* read already falls through to `clear`
("fail open rather than hard-block the whole app on a network hiccup", in
`useConnectAccessGate`), and a signed-out visitor already renders
`children` too - "we do not know yet" was the one unknown treated as a
refusal.

What it costs is honest and small: a genuinely blocked workspace sees its
app for the length of one round trip before the block card replaces it.
That is the rare path, it is self-correcting, and it is the same exposure
the failed-read branch has always accepted. Pass `loading` to opt back into
a placeholder for a gate around one small region.

**This is a UX gate, not a security boundary** - same caveat as every
other gate in this package, and the reason the trade above is available at
all. The authoritative check is `client.entitlements.getReadiness` on your
server, before you do the thing.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectAccessGateProps`](#interface-connectaccessgateprops) |

#### Returns

`Element`

***

### Function: ConnectAccountSessions() [#function-connectaccountsessions]

```ts
function ConnectAccountSessions(__namedParameters: ConnectAccountSessionsProps): Element | null;
```

Every device signed into this Connect account - not scoped to this
application, see `ConnectUserSession`'s doc comment. Owns the one
`useConnectSessions()` call for this modal; every revoke action here
mutates through it and re-reads the list itself, since nothing else
invalidates it automatically - see `mutateResource`'s doc comment.

That re-read is `invalidateResource("/sessions")` **then** `refetch()`,
and both halves are load-bearing. `mutateResource` evicts only the path it
posted to - `/sessions/revoke` - so `/sessions` keeps its cached promise,
and `refetch()` alone just re-runs the effect and is handed that same
resolved promise back. The row stayed on screen and the revoke looked like
it had silently failed, when in fact the server had done exactly as asked.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectAccountSessionsProps`](#interface-connectaccountsessionsprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectAppsGrid() [#function-connectappsgrid]

```ts
function ConnectAppsGrid(__namedParameters: ConnectAppsGridProps): Element | null;
```

The Google-style app launcher: a grid icon that opens every Connect
application this workspace has, and every one it could have.

Two sections, because they are two different actions. **Your apps** is what
the workspace can open right now - the current application pinned first and
marked, so the grid never has a hole where "you are here" should be, and a
paused subscription still listed but badged, because a dunning cycle should
not make a product look deleted. **Explore** is the rest of the active
catalog, minus anything this workspace's type could never subscribe to, and
a tile there opens that application's page on Connect.

It used to show subscriptions only, with a "Browse all applications" link
under the grid pointing at a Connect route that does not exist - so a
workspace with one subscription saw one tile and had no working way to find
anything else. Discovery belongs in the grid.

Renders nothing when signed out: a launcher with nothing to launch is just a
button that opens an empty box.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectAppsGridProps`](#interface-connectappsgridprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectAskAi() [#function-connectaskai]

```ts
function ConnectAskAi(__namedParameters: ConnectAskAiProps): Element | null;
```

Ask AI - a chat panel grounded in the signed-in workspace's Context Index,
streamed through your own bridge.

Answers are not only prose. Connect's model can call a small set of
presentation tools - `showTable`, `showChart`, `showMetrics`, `showTimeline`,
`showDetails`, `showSteps`, `showSources` - and this panel renders each one
natively: a sortable table with a CSV download, a chart, a row of metric
tiles. Values arrive raw with a format tag, so they are formatted here rather
than by the model, which is what lets a column sort numerically.

Nothing here holds a credential: it POSTs to `${basePath}/ai/chat` on your
own origin, and `createConnectHandler` is what reaches Connect. The workspace
is resolved from the caller's session there, never sent from the browser - so
a user can only ever ask about the workspace they are signed into.

```tsx
<AppShell name="Dispatch" nav={nav}>
  {children}
</AppShell>
```

`AppShell` mounts it by default. Mount it directly only when you are not
using the shell: it renders a fixed corner trigger plus a panel that is an
*in-flow flex sibling* at `lg` and up, so opening it pushes your content
aside rather than covering it. That requires it to be the last child of a
`flex` row owning the viewport height - which is exactly what `AppShell` is.
Below `lg` it is a full-screen sheet with a backdrop instead; there is no
room to push anything on a phone.

**Host setup:** markdown rendering needs `streamdown` installed, one Tailwind
`@source "…/node_modules/streamdown/dist/*.js"` line so its utility classes
are generated, and `import "streamdown/styles.css"` for the streaming
animation. The colour tokens Streamdown expects are already mapped in this
SDK's `theme.css`.

Answers require the workspace's Context Index to be turned on (a workspace
admin toggle in Connect) and your API key to carry the `ai:chat` scope.
Neither is something the browser can discover, so both surface as a message
in the panel rather than as a hidden button.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectAskAiProps`](#interface-connectaskaiprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectAvailabilityFormModal() [#function-connectavailabilityformmodal]

```ts
function ConnectAvailabilityFormModal(__namedParameters: Readonly<ConnectAvailabilityFormModalProps>): Element;
```

Creates or edits a workspace's working hours, from inside a host
application rather than a link-out to Connect.

Deliberately narrower than Connect's own Calendar settings form: one
interval per day (no split shifts), no date overrides, and the timezone is
set once at creation (from the browser's own) and shown but not editable
here afterwards. This is a first-party setup-and-edit surface, not a
second copy of the Console editor - "Manage in Connect" is still where the
rest of what a schedule can do lives.

**Not optimistic.** The write settles against the server before this
closes, the same posture every write in this package takes - see
`ConnectBookingDialog`.

This outer component only decides whether the row to edit has loaded yet;
the form itself is `ScheduleFields` below, mounted with
`key={editing?.id}` - see that component's own doc comment for why.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectAvailabilityFormModalProps`](#interface-connectavailabilityformmodalprops)\> |

#### Returns

`Element`

***

### Function: ConnectBookingCalendar() [#function-connectbookingcalendar]

```ts
function ConnectBookingCalendar(__namedParameters: Readonly<ConnectBookingCalendarProps>): Element;
```

A week of the workspace's calendar, with booking, rescheduling and
cancelling.

**Two views, and which one you get is a viewport decision, not a
preference.** A seven-column time grid needs roughly 640px before a column
is wide enough to hold a name; below that it degrades into unreadable
slivers. So the grid is `sm:` and up, and the same week renders as a grouped
agenda underneath - not a cut-down grid, a different and better shape for a
phone. Both read the same data and open the same dialogs.

Everything it does goes through the bridge as the signed-in member, so
Connect checks *their* `calendar:read` / `calendar:manage` rather than the
host application's key. A member who may look but not book gets the grid and
a refusal on the write, which is the correct shape - see `useBookingActions`.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectBookingCalendarProps`](#interface-connectbookingcalendarprops)\> |

#### Returns

`Element`

***

### Function: ConnectBookingDetail() [#function-connectbookingdetail]

```ts
function ConnectBookingDetail(__namedParameters: Readonly<{
  booking: Booking;
  eventType:   | BookableEventType
     | null;
  timeZone: string;
  onOpenChange: void;
  onChanged: void;
}>): Element;
```

One appointment, and the two things anyone opens it to do.

Rescheduling re-reads the slots with `excludeBookingId` set to this booking.
Without that the appointment blocks its own replacement, and moving it by
half an hour - the only move most people ever make - offers nothing.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `booking`: [`Booking`](/docs/reference/core#interface-booking); `eventType`: \| [`BookableEventType`](/docs/reference/core#interface-bookableeventtype) \| `null`; `timeZone`: `string`; `onOpenChange`: `void`; `onChanged`: `void`; \}\> |

#### Returns

`Element`

***

### Function: ConnectBookingDialog() [#function-connectbookingdialog]

```ts
function ConnectBookingDialog(__namedParameters: Readonly<{
  isOpen: boolean;
  onOpenChange: void;
  eventTypes: BookableEventType[];
  initialStartAt: Date;
  timeZone: string;
  onBooked: void;
}>): Element;
```

Picking a time, and then who it is for.

The slot list is read fresh every time this opens and is **never cached
across an open**: it is a snapshot of contention, and offering a time
somebody took ninety seconds ago is how two people end up in the same slot.
The server re-checks inside its transaction regardless, so a stale list
costs a refusal rather than a double booking - this only makes the refusal
rare enough to be surprising when it happens.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `isOpen`: `boolean`; `onOpenChange`: `void`; `eventTypes`: [`BookableEventType`](/docs/reference/core#interface-bookableeventtype)[]; `initialStartAt`: `Date`; `timeZone`: `string`; `onBooked`: `void`; \}\> |

#### Returns

`Element`

***

### Function: ConnectCallButton() [#function-connectcallbutton]

```ts
function ConnectCallButton(__namedParameters: ConnectCallButtonProps): Element | null;
```

Rings `phone` from the workspace's number. Needs `<ConnectCallProvider>`
above it, and a member with "Make calls" - render it only for them, since a
button the server refuses for everyone else helps nobody.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectCallButtonProps`](#interface-connectcallbuttonprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectCallingStatus() [#function-connectcallingstatus]

```ts
function ConnectCallingStatus(__namedParameters: ConnectCallingStatusProps): Element;
```

The workspace's calling at a glance - its business number, the minutes
left, and the next step - for a settings page. Reads the same availability
the Call buttons do, so the two never disagree. Buying minutes and adding a
number happen in Connect, and come back here when done.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectCallingStatusProps`](#interface-connectcallingstatusprops) |

#### Returns

`Element`

***

### Function: ConnectCallProvider() [#function-connectcallprovider]

```ts
function ConnectCallProvider(__namedParameters: ConnectCallProviderProps): Element;
```

Holds the call in progress and draws the in-call bar. See the module comment.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectCallProviderProps`](#interface-connectcallproviderprops) |

#### Returns

`Element`

***

### Function: ConnectConfirmDialog() [#function-connectconfirmdialog]

```ts
function ConnectConfirmDialog(__namedParameters: Readonly<ConnectConfirmDialogProps>): Element;
```

The confirmation in front of a destructive action.

**It closes on confirm, not on response.** Holding the modal open until the
server answers makes every delete feel like a form submission, and the error
path already has somewhere to go - a toast, or the row reappearing. A dialog
that stays open to report failure is right only where the failure is
*expected* and correctable in place (a seat cap, a last-admin invariant),
and that is a blocking form, not this.

`onConfirm` may return a promise. It is not awaited before closing, but a
rejection is caught here rather than escaping - an error thrown out of an
async transition reaches the nearest error boundary and takes the page down.

The anatomy is HeroUI's: the `Modal` root owns the open state,
`Modal.Container` the sizing, `Modal.Dialog` the focus trap. **The trigger
is a direct child of the root**, never wrapped in `Modal.Trigger`.

That wrapping is what this component used to do, and it meant the dialog
never opened for any caller passing a `<Button>` - which was all of them.
`Modal.Trigger` renders a `div role="button"` inside React Aria's
`Pressable` and relies on a plain `onClick` bubbling up to it; a React Aria
button nested inside calls `e.stopPropagation()` in its own `usePress`
click handler, so the click never arrives. Nothing failed loudly - the
press was simply eaten. The root is `DialogTrigger`, whose `PressResponder`
the trigger registers with directly, so there is no nesting and no
propagation to lose.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectConfirmDialogProps`](#interface-connectconfirmdialogprops)\> |

#### Returns

`Element`

***

### Function: ConnectCreateWorkspaceModal() [#function-connectcreateworkspacemodal]

```ts
function ConnectCreateWorkspaceModal(__namedParameters: Readonly<ConnectCreateWorkspaceModalProps>): Element;
```

Opens another workspace under the signed-in user's own tenant - a branch
office, a second business unit - from inside a host application rather
than a link-out to Connect.

**Only the tenant's owner can use this successfully.** `CreateWorkspaceUseCase`
refuses anyone who merely belongs to a colleague's tenant, with a message
naming that - someone who wants a workspace of their own uses a personal
account instead.

**The tenant's plan decides how many workspaces it may hold, and this does
not pre-check it.** There is no capacity read to show ahead of time - the
same posture Connect's own "Create workspace" button takes - so a refusal
surfaces here exactly as the server wrote it ("Your current plan covers a
single workspace - subscribe to a plan that includes additional workspaces
to open another.", or "You've used all 3 of the workspaces your plans
include. Upgrade a plan or delete a workspace to open another.").

**Does not switch the session to the new workspace.** Creating one never
does, on Connect's own screen either - see `useCreateWorkspace`'s doc
comment for how to chain a switch once you have the result, and guard that
chain on `inAudience` the same way `<ConnectWorkspaceSwitcher>` already
does for every other row.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectCreateWorkspaceModalProps`](#interface-connectcreateworkspacemodalprops)\> |

#### Returns

`Element`

***

### Function: ConnectCursorPagination() [#function-connectcursorpagination]

```ts
function ConnectCursorPagination(__namedParameters: Readonly<ConnectCursorPaginationProps>): Element;
```

Next / previous for a cursor-paginated list.

Cursor rather than page numbers, and the component shape follows from that:
there is no "page 7 of 42" because a keyset-paginated list does not know how
many pages there are without a second count query over the same filter -
which is a billed index scan for a number nobody acts on. What a user does
act on is "more" and "back", and those are exact.

The count is announced in a live region so a screen-reader user learns that
filtering or paging changed the result set. Without it the table silently
swaps its contents.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectCursorPaginationProps`](#interface-connectcursorpaginationprops)\> |

#### Returns

`Element`

***

### Function: ConnectEventTypeFormModal() [#function-connecteventtypeformmodal]

```ts
function ConnectEventTypeFormModal(__namedParameters: Readonly<ConnectEventTypeFormModalProps>): Element;
```

Creates or edits an appointment type, from inside a host application
rather than a link-out to Connect.

**Needs a schedule to exist first** - the picker reads `useAvailabilitySchedules`
and renders "Set up availability first" with nothing to submit if none
exists yet. Pair this with `ConnectAvailabilityFormModal` in a setup flow
rather than assuming one.

**That gate waits for the read, and the order matters.** Both bridge reads
answer `[]` while they are in flight, so testing `schedules.length === 0`
first showed "Set up availability first" to everybody who opened this -
including, for a second or two, somebody pressing Edit on an appointment
type that provably *has* a schedule. A load state wearing an empty state's
words is worse than a spinner: it reads as a verdict, and on a slow
connection it is the only thing the modal ever says. A failed read is named
as a failure for the same reason - it is not the same fact as "none exist".

**Host is a specific member, never a team.** There is no groups read in
this bridge, so a round-robin team host stays a Console-only capability -
defaults to whoever is signed in on create, with an optional picker from
the workspace's own roster. **Editing a group-hosted row is refused
inline** - the modal shows a notice instead of a form, because this
surface cannot represent (and so cannot safely resend) a team host.

This outer component only decides *which* message or form to show; the
form itself is `EventTypeFields` below, mounted with `key={editing?.id}` -
see that component's own doc comment for why.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectEventTypeFormModalProps`](#interface-connecteventtypeformmodalprops)\> |

#### Returns

`Element`

***

### Function: ConnectFeatureGate() [#function-connectfeaturegate]

```ts
function ConnectFeatureGate(__namedParameters: ConnectFeatureGateProps): Element;
```

Renders `children` only when the workspace's plan includes `feature`.

```tsx
<ConnectFeatureGate feature="advanced_reports" title="Reports are on Growth">
  <Reports />
</ConnectFeatureGate>
```

Like every gate in this package, it is a **UX gate, not a security
boundary**: the entitlement is read through your own origin and the browser
can ignore it. Check `client.entitlements.hasFeature(workspaceId, feature)` on the
server before doing the thing the feature unlocks.

Renders `loading` - not `fallback` - while the answer is unknown, so a paying
customer never sees an upsell for something they already have.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectFeatureGateProps`](#interface-connectfeaturegateprops) |

#### Returns

`Element`

***

### Function: ConnectFilterTabs() [#function-connectfiltertabs]

```ts
function ConnectFilterTabs<TValue>(__namedParameters: Readonly<ConnectFilterTabsProps<TValue>>): Element;
```

A single-select filter strip - the "All / Open / Won / Lost" row above a
list.

Real radio semantics rather than a row of buttons: it is a single choice
from a closed set, so arrow keys should move between options and the
selected one should be announced as selected. A group of `<button>`s gets
neither, and that is the usual hand-rolled shape.

Wraps rather than scrolling on a narrow viewport. See the note on the
container below: a horizontal scroller nested in a vertically scrolling
page hides its overflow rather than deferring it.

#### Type Parameters

| Type Parameter |
| ------ |
| `TValue` *extends* `string` |

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectFilterTabsProps`](#interface-connectfiltertabsprops)\<`TValue`\>\> |

#### Returns

`Element`

***

### Function: ConnectGrievanceNotice() [#function-connectgrievancenotice]

```ts
function ConnectGrievanceNotice(__namedParameters: Readonly<ConnectGrievanceNoticeProps>): Element | null;
```

The DPDP s.13 grievance channel, rendered.

Every application on the platform is a Data Processor under one notice, so
the wording lives here rather than being retyped per application - a user
comparing two applications' privacy pages should not find two different
accounts of who their Data Fiduciary is.

Renders nothing at all while loading and nothing on failure, rather than a
skeleton or an error. That is unusual here and deliberate: this component
is almost always inside a larger privacy page that has its own content, and
a red error box in the middle of a legal notice reads as the notice itself
being broken. The address is also on the public web either way. Pass
`useConnectCompanyProfile()` directly to your own layout if you want to
handle those states yourself.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectGrievanceNoticeProps`](#interface-connectgrievancenoticeprops)\> |

#### Returns

`Element` \| `null`

***

### Function: ConnectHomeButton() [#function-connecthomebutton]

```ts
function ConnectHomeButton(__namedParameters: Readonly<{
  className?: string;
}>): Element;
```

A plain "Open Connect" button, for a footer or a help menu.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `className?`: `string`; \}\> |

#### Returns

`Element`

***

### Function: ConnectIntegrationsModal() [#function-connectintegrationsmodal]

```ts
function ConnectIntegrationsModal(__namedParameters: ConnectIntegrationsModalProps): Element | null;
```

A grid of every Composio-hosted toolkit this workspace can connect -
Google Calendar, Slack, Twitter/X, whatever a platform admin enabled in
Connect's own Console. Each tile carries its own connect/reconnect/
disconnect action and its own pending state, independent of every other
tile - see the doc comment on `pendingSlug` below for why that matters.

Not optimistic: a tile stays whatever it already was after "Connect" is
pressed, because the connection isn't settled until Composio's webhook
confirms it - `mutateResource` here only ever gets as far as `pending`
before the browser navigates away to complete the provider's OAuth screen.

Renders nothing when signed out, same as `ConnectAppsGrid`.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectIntegrationsModalProps`](#interface-connectintegrationsmodalprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectInvoicesButton() [#function-connectinvoicesbutton]

```ts
function ConnectInvoicesButton(__namedParameters: Readonly<{
  className?: string;
}>): Element | null;
```

Invoices and payment history, on Connect.

A deep link rather than a component that lists them, and that is not a
placeholder: nothing in `/api/v1/application/*` exposes invoices, so an SDK-side
list would have nothing to read. It is also the right answer regardless -
a GST tax invoice is a legal document with a fixed format, downloadable and
emailed from one place, and a partner-rendered copy of one would be a second
version of a record that must have exactly one.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `className?`: `string`; \}\> |

#### Returns

`Element` \| `null`

***

### Function: ConnectLinkButton() [#function-connectlinkbutton]

```ts
function ConnectLinkButton(__namedParameters: Readonly<ConnectLinkButtonProps>): Element;
```

A HeroUI `Button` that renders a link.

HeroUI v3's `Button` has no `as` prop, so `<Button as={Link}>` does not type
check and does not work - this is the supported route, via `render`.

The cast is the same one HeroUI's own "Render Function" example needs:
`render` types its argument as the props of the element `Button` would have
emitted, which is a `<button>`, including a `ref` typed to
`HTMLButtonElement`. Spreading that onto an anchor is sound at runtime and
unrepresentable in the type, which is what the cast is for.

Why passing every handler through is safe for navigation (React Aria
3.51 `usePress`): nothing on the press path calls `preventDefault` on an
anchor's click except when the button is **disabled** - which is the one
case it should stop. A mouse click is turned into `onPress` and allowed to
proceed; `Enter` is left to the browser (`shouldPreventDefaultKeyboard` is
false for an `<a href>`), whose native click then does the same; `Space` is
not treated as a press on a link at all, matching link semantics. `next/link`
calls the passed `onClick` first and navigates unless `defaultPrevented`, so
it still navigates. The click handler does `stopPropagation`, as every React
Aria button does - ancestors' `onClick` do not see it.

React Aria still logs one development-only warning - "Expected \<button>,
got \<a>" - because the ref now reaches a real element and it can see the
tag. That is the honest description of this component, and the alternative
(dropping the ref) is what produced the two warnings this replaced.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectLinkButtonProps`](#type-alias-connectlinkbuttonprops)\> |

#### Returns

`Element`

***

### Function: ConnectLoginButton() [#function-connectloginbutton]

```ts
function ConnectLoginButton(__namedParameters: ConnectAuthButtonProps): Element | null;
```

"Continue with Connect" - the federated sign-in button, in the shape people
already know from Google and GitHub: neutral surface, brand mark on the
left, one line of text.

`variant="secondary"` by default rather than `primary`, on purpose. A
federated button that outshouts the host app's own primary action reads as
an ad; every mature identity button is quiet. Pass `variant` to override if
Connect *is* the primary path in your flow.

Hidden while a session is still loading, so a signed-in user never sees a
Login button flash before their avatar resolves.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectAuthButtonProps`](#interface-connectauthbuttonprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectLogo() [#function-connectlogo]

```ts
function ConnectLogo(__namedParameters: ConnectLogoProps): Element;
```

The shared platform lockup: an application's own glyph on the CosmoOps badge,
`cosmo·ops` beside it, and the application name underneath.

This is what makes a family of application platforms look like one product. Each
platform passes only what differs - its `name` and its `icon` - and inherits
the mark geometry, the wordmark treatment, the separator, and the accent from
one place. Change the lockup here and every platform follows.

Sizes are `em`-based throughout, so the whole lockup scales from the
wrapper's font size: `className="text-base"` renders it half again as large
as `text-sm`, with the badge, gap and tracking all in proportion. That is
why there is no `size` prop.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectLogoProps`](#interface-connectlogoprops) |

#### Returns

`Element`

***

### Function: ConnectManageSubscriptionButton() [#function-connectmanagesubscriptionbutton]

```ts
function ConnectManageSubscriptionButton(__namedParameters: Readonly<{
  className?: string;
}>): Element;
```

The "Manage subscription" action on its own, for layouts that want it
visible rather than buried in the account menu - a billing banner, an empty
state, a settings page.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `className?`: `string`; \}\> |

#### Returns

`Element`

***

### Function: ConnectMark() [#function-connectmark]

```ts
function ConnectMark(__namedParameters: Readonly<{
  className?: string;
}>): Element;
```

The Connect mark - concentric rings on a solid accent badge.

Painted from `--accent` / `--accent-foreground`, the same HeroUI tokens the
host app themes, so the badge tracks whatever palette an application platform has
configured rather than carrying a hardcoded CosmoOps green into someone
else's brand.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `className?`: `string`; \}\> |

#### Returns

`Element`

***

### Function: ConnectNoSubscriptionCard() [#function-connectnosubscriptioncard]

```ts
function ConnectNoSubscriptionCard(__namedParameters: ConnectNoSubscriptionCardProps): Element;
```

The "you are not on a plan yet" card.

Points at your **own** `/plans` page rather than opening Connect, and that is
the deliberate part: choosing a plan is browsing, it belongs in your product
where your pricing story is, and only the final confirmation needs to happen
on Connect. Sending someone straight out of the app to pick is a context
switch two steps too early.

Rendered on its own it is just a card; `ConnectSubscriptionGate` is the usual
way in, since it picks this only when there is genuinely nothing to show.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectNoSubscriptionCardProps`](#interface-connectnosubscriptioncardprops) |

#### Returns

`Element`

***

### Function: ConnectOneTap() [#function-connectonetap]

```ts
function ConnectOneTap(__namedParameters: ConnectOneTapProps): Element | null;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectOneTapProps`](#interface-connectonetapprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectPlans() [#function-connectplans]

```ts
function ConnectPlans(__namedParameters: ConnectPlansProps): Element;
```

The plan table: monthly/annual toggle, features, add-ons, the discount
actually in effect, and the subscribe / upgrade / downgrade flows.

#### Where the flow runs, and where it finishes

The *flow* is here: picking a plan, seeing what it costs with GST, being
told what happens to the cycle you are part-way through, and confirming.
The *payment* is not, and that is deliberate. A plan change is not one
write - it re-runs the readiness gates, prices proration against
accumulated credit, re-checks the RBI mandate ceiling for the new amount,
and issues a GST invoice. Connect owns that sequence end to end; a
partner-side "change plan" call would be a second implementation of it
that drifts. It would also mean a browser holding the partner's
credentials could move money, which is the one thing the bridge exists to
prevent.

So confirming opens Connect - on the *exact* confirmation screen for the
plan just picked, via an intercepted parallel route, not a catalogue the
customer has to search again - and this component watches `/entitlements`
until the change lands, then repaints itself. See `useConnectCheckout`.

Coupon codes work the same way: the field carries the code into Connect's
checkout, where it is validated and applied against the real cart. Anything
this component claimed about a code would be a guess it cannot honour.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectPlansProps`](#interface-connectplansprops) |

#### Returns

`Element`

***

### Function: ConnectPlansPage() [#function-connectplanspage]

```ts
function ConnectPlansPage(__namedParameters: ConnectPlansPageProps): Element;
```

A complete `/plans` page, assembled from the pieces so the host app's route
file is one line:

```tsx
// app/plans/page.tsx
"use client";
export { ConnectPlansPage as default } from "@cosmoops/connect-sdk/client";
```

The assembly is the value here, not the layout. Three things have to be on
this page and are easy to leave off:

1. **The readiness banner, above the grid.** A workspace that still needs
   KYC can press Subscribe all day and be refused at the last step. Saying so
   before they pick is the difference between a page that works and one that
   ends in a rejected charge.
2. **What they're on now**, so "Upgrade" on a card has something to mean.
3. **A signed-out state that is a login button**, not an empty grid - the
   catalogue reads through the bridge, which needs a session.

Every prop `ConnectPlans` takes passes straight through, and each piece is
exported on its own for an app that wants a different arrangement.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectPlansPageProps`](#interface-connectplanspageprops) |

#### Returns

`Element`

***

### Function: ConnectPlatformGate() [#function-connectplatformgate]

```ts
function ConnectPlatformGate(__namedParameters: ConnectPlatformGateProps): Element;
```

Renders `children` only for a Connect platform staff session.

```tsx
<ConnectPlatformGate>
  <LinkMasterConsole />
</ConnectPlatformGate>
```

Staff are Connect's own operators - `platform_admin`, `platform_manager`,
`developer` - signing into *your* application. They belong to no workspace,
so a staff session carries no `workspaceId`, no subscription and no member
role; see `SessionAccess.staff`. Staff access is off by default and turned on
per application by a Connect platform admin, so this gate renders nothing
until that happens.

**This is a UX gate, not a security boundary** - the same caveat every gate
in this package carries, and it matters more here than anywhere else,
because what is behind this one is usually an operator console. The browser
can ignore this component entirely. Every route it reveals must re-check
`staff` on your own server:

```ts
const { staff, role } = await client.identity.getUser(sessionToken);
if (!staff) return forbidden();
```

Renders `loading` - not `fallback` - while the session is unknown, so an
operator never flickers through the customer layout on the way in.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectPlatformGateProps`](#interface-connectplatformgateprops) |

#### Returns

`Element`

***

### Function: connectPortalUrl() [#function-connectportalurl]

```ts
function connectPortalUrl(
   appUrl: string, 
   clientId: string, 
   path: string): string;
```

Any Connect portal page, with the context it needs to offer a way back
here - `connectPortalUrl(appUrl, clientId, "/profile")`,
`connectPortalUrl(appUrl, clientId, "/calendar/settings")`, and so on.

Connect answers any of these paths for a signed-in user on the bare URL -
these two params only add the `"Back to <your app>"` banner
(the hand-off strip in `ShellAnnouncements` on Connect's side). It validates `return_to`
against this `client_id`'s registered redirect origins and silently drops
the banner if they don't match, so a wrong or stale `appUrl`/`clientId`
pairing degrades to what linking there did before rather than erroring.

`window.location.href` rather than a configured home URL: the point is to
come back to the page they left, and only the browser knows which one that
is.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `appUrl` | `string` |
| `clientId` | `string` |
| `path` | `string` |

#### Returns

`string`

***

### Function: ConnectProfileButton() [#function-connectprofilebutton]

```ts
function ConnectProfileButton(__namedParameters: Readonly<{
  className?: string;
}>): Element | null;
```

The "Profile" action on its own - the third of the three Connect
destinations, alongside `ConnectManageSubscriptionButton` (Billing) and
`ConnectHomeButton`.

It was reachable only from inside `ConnectUserMenu`, which meant an app that
builds its own account menu had to hardcode `${appUrl}/profile` to offer it -
and got no `return_to`, so the user had no way back.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `className?`: `string`; \}\> |

#### Returns

`Element` \| `null`

***

### Function: ConnectProfileModal() [#function-connectprofilemodal]

```ts
function ConnectProfileModal(__namedParameters: ConnectProfileModalProps): Element | null;
```

The account modal with **no trigger of its own**, driven by the host.

For an application that already has an account control it wants to keep -
an avatar in its own chrome, a "Manage account" row in its own settings
page - and wants Connect's modal behind it rather than rebuilding identity,
sessions and sign-out. `ConnectUserButton` is the same modal with the
trigger supplied; reach for this one only when that trigger is in the way.

Renders nothing when signed out, open or not - there is no account to show.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectProfileModalProps`](#interface-connectprofilemodalprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectProvider() [#function-connectprovider]

```ts
function ConnectProvider(__namedParameters: ConnectProviderProps): Element;
```

Wraps the part of your app that shows Connect UI.

Holds one session read, shared by every component below it, so a page with a
user menu, an apps grid and a plan table makes one request rather than three.
There is no API key here and there cannot be: everything goes to your own
origin, where `createConnectHandler` holds the credential.

```tsx
<ConnectProvider>
  <App />
</ConnectProvider>
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectProviderProps`](#interface-connectproviderprops) |

#### Returns

`Element`

***

### Function: ConnectReadinessBanner() [#function-connectreadinessbanner]

```ts
function ConnectReadinessBanner(__namedParameters: ConnectReadinessBannerProps): Element | null;
```

Every gate between this workspace and your application, with the way out of each.

This is the component that keeps a blocked customer from staring at a broken
app. `getReadiness` resolves subscription state, KYC/KYB, a chargeable
payment method and seat headroom in one call, and **each blocker carries a
`resolveUrl` into the exact Connect screen that fixes it** - so being blocked
comes with somewhere to go rather than a refusal.

Render it high on any page your application gates. It draws nothing at all when
the workspace is ready, so it is safe to leave mounted permanently.

Two deliberate refusals:

- **Unrecognised codes are still shown, and still treated as blocking.**
  Connect adds codes without a major version (see `ReadinessBlockerCode`), so
  a switch that only knew today's codes would silently drop tomorrow's - the
  one failure mode where a customer is locked out with no explanation.
- **`message` is rendered, never parsed.** It is human-facing and subject to
  rewording; branching on its text would break on a copy edit.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectReadinessBannerProps`](#interface-connectreadinessbannerprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectReadinessGate() [#function-connectreadinessgate]

```ts
function ConnectReadinessGate(__namedParameters: ConnectReadinessGateProps): Element;
```

Renders `children` only once the workspace clears every blocking gate.

The hard version of `ConnectReadinessBanner`, for a route that genuinely
cannot function while blocked. Prefer the banner where the page still does
something useful - a gate that hides a whole screen behind a verification
notice is a worse experience than one that lets people look around while
they sort it out.

**This is a UX gate, not a security boundary.** The readiness read reaches
your own origin and a determined browser can ignore whatever it says. The
authoritative check is `client.entitlements.getReadiness` on your server,
before you do the thing.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectReadinessGateProps`](#interface-connectreadinessgateprops) |

#### Returns

`Element`

***

### Function: ConnectRegisterButton() [#function-connectregisterbutton]

```ts
function ConnectRegisterButton(__namedParameters: ConnectAuthButtonProps): Element | null;
```

The same button pointed at registration (`prompt=create`).

Only the starting screen differs - each screen links to the other carrying
the same in-flight request, and anyone who already has a Connect session
skips straight to consent either way. So shipping both buttons costs nothing
and matches what users expect from a "Sign up" / "Log in" pair.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectAuthButtonProps`](#interface-connectauthbuttonprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectSeatUsage() [#function-connectseatusage]

```ts
function ConnectSeatUsage(__namedParameters: Readonly<{
  plan: SubscribedPlan;
  detail: SubscriptionDetail;
  className?: string;
}>): Element | null;
```

Seats used against how many may be used - what a `"fixed"` plan includes, or
the seats a `"per-seat"` subscription has bought (`detail.seatCapacity`).

Only meaningful for `"fixed"` and `"per-seat"` - `"unlimited"` has nothing to
count against, so it renders nothing rather than a meter that is always
empty. On `"per-seat"` a full count says what one more costs, because the
next assignment is refused until a seat is bought.

`detail.seatCount` is a live count of the members holding a role for this
application. It used to be the subscription's last-billed snapshot, which
only ever moved on a `"per-seat"` plan - so a `"fixed"` single-seat plan
read "0 of 1 seat used" permanently, including when its one seat was taken
and Connect was already refusing the next assignment.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `plan`: [`SubscribedPlan`](/docs/reference/core#interface-subscribedplan); `detail`: [`SubscriptionDetail`](/docs/reference/core#interface-subscriptiondetail); `className?`: `string`; \}\> |

#### Returns

`Element` \| `null`

***

### Function: ConnectShellProvider() [#function-connectshellprovider]

```ts
function ConnectShellProvider(__namedParameters: Readonly<{
  value: ConnectShellContextValue;
  children: ReactNode;
}>): Element;
```

Shared state for every part of the shell, so `ConnectSidebarNav`,
`ConnectWorkspaceSwitcher` and `ConnectUserButton` all know whether the rail
is collapsed without the host threading a prop through three layers.

This is the piece that makes the shell *composable* rather than only
configurable: the parts are exported and can be rearranged, and they still
agree about collapse, routing and the mobile drawer because they read one
context instead of one another's props.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `value`: [`ConnectShellContextValue`](#interface-connectshellcontextvalue); `children`: `ReactNode`; \}\> |

#### Returns

`Element`

***

### Function: ConnectSidebar() [#function-connectsidebar]

```ts
function ConnectSidebar(__namedParameters: ConnectSidebarProps): Element;
```

The application rail: a fixed-width column that animates between an
icon-only 64px and a labelled 264px.

**Why width, and nothing else, animates.** Every row inside puts its glyph
on the same 32px centre in both states (see `RAIL_COLLAPSED`), and every
label is clipped rather than re-measured (see `SIDEBAR_ROW`). So the only
property in flight is this element's width: no child moves, no text
re-wraps, and there is no per-frame layout pass on the rows. That is why the
collapse looks smooth rather than merely fast.

Hidden below `md`. The mobile drawer renders the same children with
`collapsed: false`, because a 64px icon rail on a phone is a worse drawer,
not a smaller one.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectSidebarProps`](#interface-connectsidebarprops) |

#### Returns

`Element`

***

### Function: ConnectSidebarBody() [#function-connectsidebarbody]

```ts
function ConnectSidebarBody(__namedParameters: Readonly<{
  children: ReactNode;
}>): Element;
```

The rail's scrolling region, for the primary navigation. Everything else in
the sidebar is `shrink-0`, so this is the only part that ever gives way.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `children`: `ReactNode`; \}\> |

#### Returns

`Element`

***

### Function: ConnectSidebarFooter() [#function-connectsidebarfooter]

```ts
function ConnectSidebarFooter(__namedParameters: Readonly<{
  children: ReactNode;
  separated?: boolean;
}>): Element;
```

A pinned region at the foot of the rail - the secondary nav, the launcher,
the account row.

`separated` draws the hairline above it. The account row wants one; a
secondary nav sitting directly on top of the account row does not, or the
rail ends in two rules a few pixels apart.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `children`: `ReactNode`; `separated?`: `boolean`; \}\> |

#### Returns

`Element`

***

### Function: ConnectSidebarHeader() [#function-connectsidebarheader]

```ts
function ConnectSidebarHeader(__namedParameters: ConnectSidebarHeaderProps): Element;
```

The brand row, and the rail's own expand/collapse control.

**Two states, one DOM.** The badge is mounted once and never re-created; the
wordmark fades beside it and the collapse button fades at the trailing edge.
Swapping which *element* renders the brand would remount it halfway through
the width animation and flicker.

Collapsed, hovering or focusing the badge swaps it for the expand glyph, so
the rail needs no permanent control eating a row. Expanded, the control sits
at the header's trailing edge, where it can be aimed at without covering the
brand. The swap is pure CSS on a `group` - hovering re-renders nothing,
which is what keeps it instant.

The trade this makes, deliberately: **collapsed, the lockup is no longer a
link home.** It has become the expand control, and one target that navigates
on click but expands on click-after-hover would be worse than either. Put
home in the nav; every application already does.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectSidebarHeaderProps`](#interface-connectsidebarheaderprops) |

#### Returns

`Element`

***

### Function: ConnectSidebarNav() [#function-connectsidebarnav]

```ts
function ConnectSidebarNav(__namedParameters: ConnectSidebarNavProps): Element;
```

The sidebar's nav column on its own - sections, items, one level of
children, and the active highlight.

`AppShell` renders this inside its rail and its mobile drawer, and it
is exported for a host that wants the shell's navigation without its chrome:
the same items, the same active resolution, dropped into a layout you
already have.

Every prop is optional except `nav`, because inside a shell they are all
inherited from it. Passing one wins over what the shell says - which is how
a second nav column (see `secondaryNav`) can share the rail's collapse state
while keeping its own items.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectSidebarNavProps`](#interface-connectsidebarnavprops) |

#### Returns

`Element`

***

### Function: ConnectSidebarRow() [#function-connectsidebarrow]

```ts
function ConnectSidebarRow(__namedParameters: ConnectSidebarRowProps): Element;
```

A pressable row in the rail that is not a nav item - the launcher, the
account button, anything a host adds to the footer.

Exported so a host's own row lines up with the SDK's. It applies the same
32px glyph centre and the same label fade, which is what stops a partner's
"Help" row drifting sideways while every row above it holds still.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectSidebarRowProps`](#type-alias-connectsidebarrowprops) |

#### Returns

`Element`

***

### Function: ConnectSignOutButton() [#function-connectsignoutbutton]

```ts
function ConnectSignOutButton(__namedParameters: Readonly<{
  onSignOut?: () => void;
  className?: string;
}>): Element | null;
```

Sign out on its own, for a settings page that wants it as a destructive row.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<\{ `onSignOut?`: () => `void`; `className?`: `string`; \}\> |

#### Returns

`Element` \| `null`

***

### Function: ConnectSubscribeDialog() [#function-connectsubscribedialog]

```ts
function ConnectSubscribeDialog(__namedParameters: Readonly<ConnectSubscribeDialogProps>): Element;
```

The review step before a customer is sent to Connect.

It exists because the hand-off is a context switch, and a context switch
the customer did not expect reads as being dumped somewhere. So this says
what they are about to buy, what it costs with GST, what happens to the
cycle they are part-way through, and - plainly - that the payment itself
happens on Connect. Then one button, which opens it.

**Every number here is a preview, and deliberately never the last word.**
The authoritative price is computed by Connect against the real cart:
proration is priced against the actual cycle, a coupon is validated
against the plan it is being applied to, trial eligibility depends on
whether this workspace has ever subscribed to this application before, and
an accumulated credit is deducted. None of that is knowable from the
partner's origin, so this component states the list price and says where
the final figure comes from rather than quietly guessing at one.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectSubscribeDialogProps`](#interface-connectsubscribedialogprops)\> |

#### Returns

`Element`

***

### Function: ConnectSubscriptionCard() [#function-connectsubscriptioncard]

```ts
function ConnectSubscriptionCard(__namedParameters: ConnectSubscriptionCardProps): Element;
```

The current subscription at a glance: plan, what it costs, what happens next,
and one way into Connect to change it.

The counterpart to `ConnectNoSubscriptionCard` - the same slot in the
signed-up state - so an account or settings page can render this and get the
right thing either way.

Dates are formatted, never computed. "Renews on 3 Mar" is a fact from the
wire; "renews in 12 days" would be a clock read during render, which is
impure and re-renders wrong.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectSubscriptionCardProps`](#interface-connectsubscriptioncardprops) |

#### Returns

`Element`

***

### Function: ConnectSubscriptionGate() [#function-connectsubscriptiongate]

```ts
function ConnectSubscriptionGate(__namedParameters: ConnectSubscriptionGateProps): Element;
```

Renders `children` for a subscribed workspace, and the "pick a plan" card for
everyone else.

**Never renders the fallback while loading.** That flash - "you're not
subscribed" for 200ms, then the real content - is the specific bug this
component exists to make impossible, and it is what hand-rolled versions of
it get wrong: the honest states are "still asking" and "asked, and the answer
is no", and only the second one is a sales pitch.

**This is a UX gate, not a security boundary** - same caveat as
`ConnectReadinessGate`. Anything that must not happen without a subscription
is checked on your server with `client.entitlements.hasFeature`.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectSubscriptionGateProps`](#interface-connectsubscriptiongateprops) |

#### Returns

`Element`

***

### Function: ConnectTrialBanner() [#function-connecttrialbanner]

```ts
function ConnectTrialBanner(__namedParameters: ConnectTrialBannerProps): Element | null;
```

A trial that is running, and when it converts.

Renders only while trialing, so it can live permanently in a layout. It says
the charge date rather than a countdown for the same reason
`ConnectSubscriptionCard` does - a countdown means reading the clock while
rendering.

The wire carries `isTrialing` but no trial *end* date; `nextBillingDate` on a
trialing subscription is the first charge, which is the date a customer
actually cares about anyway.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectTrialBannerProps`](#interface-connecttrialbannerprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectUsageMeter() [#function-connectusagemeter]

```ts
function ConnectUsageMeter(__namedParameters: ConnectUsageMeterProps): Element | null;
```

How much of each consumable add-on this workspace has left.

Reads the balance Connect actually debits, so the number here and the number
`consumeAddon` enforces cannot disagree. Before the `addons:read` endpoint
existed this component was impossible to write honestly: an application could
spend an allowance down on every request and the only thing the API exposed
was the list of add-on *ids* on the subscription.

**It shows `balance` against `includedPerCycle`, and never a "used" figure.**
With `carryForward` an unspent balance survives the cycle boundary, so
`included - balance` can go negative - a meter that runs backwards. Real
consumption for a cycle is a ledger question, and computing it from a
balance would be a guess rendered as a fact.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectUsageMeterProps`](#interface-connectusagemeterprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectUserButton() [#function-connectuserbutton]

```ts
function ConnectUserButton(__namedParameters: ConnectUserButtonProps): Element | null;
```

The signed-in account, as a control that opens the account modal.

In `"row"` form this is the last thing in the sidebar, which is where an
account control belongs: it is the one item in the rail that is about *you*
rather than about the product, and the foot of a column is where every
application a customer already uses puts that. It collapses with the rail -
the avatar holds its position and the name fades - because the avatar alone
is still a complete account control, which is exactly what an icon rail
needs.

Renders nothing when signed out. Pair it with `<ConnectLoginButton>`, which
renders nothing when signed *in*, and the two swap with no branch in your
own layout.

On the HeroUI shape: the trigger is a plain `<Button>` as a **direct child**
of `<Modal>`, not wrapped in `<Modal.Trigger>`. `Dropdown.Trigger` - and by
the same construction `Modal.Trigger` - is itself a React Aria `Button`, so
wrapping one produces a button inside a button: invalid markup, and press
handling stops working.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectUserButtonProps`](#interface-connectuserbuttonprops) |

#### Returns

`Element` \| `null`

***

### ~~Function: ConnectUserMenu()~~ [#function-connectusermenu]

```ts
function ConnectUserMenu(__namedParameters: ConnectUserMenuProps): Element;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectUserMenuProps`](#interface-connectusermenuprops) |

#### Returns

`Element`

#### Deprecated

Renamed to `ConnectUserButton` in 0.12 - it is one button that
opens a modal, and calling it a "menu" had people looking for a dropdown
that never existed. `compact` maps to `variant="compact"`, and the old
default (avatar plus name) to `variant="inline"`. This alias will be removed
in a later minor.

***

### Function: ConnectVisitorChat() [#function-connectvisitorchat]

```ts
function ConnectVisitorChat(__namedParameters: ConnectVisitorChatProps): Element | null;
```

The public chat bubble: a visitor with no account asks about your product,
books a call, or gets started.

#### It runs on pages that have no `ConnectProvider`

Deliberately, and it is the main thing separating this component from every
other one here. A marketing page usually has no session to hold and nothing
on it that needs one, so requiring the provider would mean wrapping a
landing page in an auth context to put a chat bubble on it. It reads the
context through `useConnectOptional` and uses it only for `basePath` and the
injected `fetch`; both have props, and both have defaults.

#### Nothing happens until somebody opens it

The conversation is minted on first open, never on mount. On a busy marketing
site mounting would mean one Firestore write and one wasted session row per
*page view*, for a bubble most visitors never touch - and the bubble itself
needs no server state to render. The greeting arrives with that first call
and is stored copy rather than generated, so opening costs one fast round
trip and the visitor is reading real words immediately.

#### It is built for a phone first

Open, it is a full-height sheet on a narrow viewport and an anchored panel
from `sm` up - a marketing chat that covers a desktop browser is harder to
use, and one that does not cover a phone has nowhere to put a keyboard.
`h-dvh` rather than `h-screen`, so the sheet tracks the visual viewport when
the keyboard opens; `env(safe-area-inset-bottom)` on the composer, so the
send button is not under a home indicator.

The composer's input type is **the server's decision**, not a guess from what
the assistant said - see `VisitorField`. That is what puts an `@` on the
keyboard when an email is wanted and lets iOS offer a verification code
straight from the notification.

#### The times are drawn from the tool result, never the prose

A turn that looked up availability carries the slots as structured data, and
the buttons come from that. So a time the visitor taps is one the server
offered, which is the property `/visitor/book` relies on and cannot check for
itself.

#### Replies render as text, not markdown

`<ConnectAskAi>` lazy-loads a markdown renderer because it answers questions
about a workspace's documents, where tables and code blocks carry meaning.
This answers three-sentence questions about a product on a marketing page.
Shipping 462 KB of renderer to a landing page for prose that is deliberately
kept to a couple of lines is the wrong trade, so line breaks are preserved
and nothing else is parsed.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectVisitorChatProps`](#interface-connectvisitorchatprops) |

#### Returns

`Element` \| `null`

***

### Function: ConnectWorkspaceSwitcher() [#function-connectworkspaceswitcher]

```ts
function ConnectWorkspaceSwitcher(__namedParameters: Readonly<ConnectWorkspaceSwitcherProps>): Element | null;
```

Switches which workspace this application is looking at.

**Rendered in the `AppShell` sidebar header by default**, directly
under the brand - which workspace you are in scopes everything below it, so
it reads as a heading for the nav rather than as one more destination.

It decides for itself whether to appear: an account with one workspace is
shown nothing by default, because a switcher offering one option teaches
the user nothing and costs a row of chrome on every screen. That is the
opposite of Connect's own switcher, which always renders - Connect is where
a workspace is *created*, so hiding it at one workspace would guarantee
nobody ever reached two. `alwaysShow` is the escape hatch for a partner
application that wants that same discoverability without becoming Connect:
it keeps the dropdown open below two workspaces and adds a row that opens
`<ConnectCreateWorkspaceModal>` inline, auto-switching into the result when
that is possible - see `handleCreated` below.

**Authorization is the data, not a prop.** The list comes from the caller's
own session (`GET /workspaces` takes no user id), so a workspace the user
may not reach is simply not in it. There is deliberately no `roles` prop to
pass or forget - the only way to render a row is for Connect to have
returned it.

Rows the user *belongs to* but cannot use here - a workspace outside the
application's audience, or one with no subscription - are shown **disabled
with the reason** rather than hidden. Hiding them reads as data loss to the
person who owns the workspace; `switch` refuses the same two cases, so the
label and the outcome always agree.

A switch rotates the session token. The bridge persists the new one in the
host application's own session store and the token never reaches this
component - so the only thing to do afterwards is reload, which is what
`onSwitched` defaults to.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | `Readonly`\<[`ConnectWorkspaceSwitcherProps`](#interface-connectworkspaceswitcherprops)\> |

#### Returns

`Element` \| `null`

***

### Function: ConnectWorkspaceTypeGate() [#function-connectworkspacetypegate]

```ts
function ConnectWorkspaceTypeGate(__namedParameters: ConnectWorkspaceTypeGateProps): Element;
```

Renders `children` only for a workspace of the given type.

```tsx
<ConnectWorkspaceTypeGate type="business">
  <TeamMembersTable />
</ConnectWorkspaceTypeGate>
```

The point is dead controls, not access: an `"individual"` workspace has
exactly one seat and can never have another, so a members table, an invite
button and a seat meter are all things that can only ever fail there.
Hiding them is a better answer than letting someone press one and read an
error about an account shape they did not choose.

This is emphatically **not** the audience gate. It does not know what your
application is sold to and will happily render a business-only subtree to a
business workspace that has no business being in your application at all -
that is `<ConnectAccessGate>`'s job, off the `audience_mismatch` readiness
blocker. Use this one *inside* an application both audiences can reach, to
vary what each sees.

Like every gate in this package it is a **UX gate, not a security
boundary**: the browser can ignore it. Anything that must not happen for an
individual workspace has to be refused on your server too.

Renders `loading` - not `fallback` - while the session is unknown, so a
business account never flickers through the individual-account layout.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `__namedParameters` | [`ConnectWorkspaceTypeGateProps`](#interface-connectworkspacetypegateprops) |

#### Returns

`Element`

***

### Function: dayColumns() [#function-daycolumns]

```ts
function dayColumns(
   anchor: Date, 
   timeZone: string, 
   count: number, 
   now: Date): DayColumn[];
```

`count` consecutive days starting at the local day `anchor` falls on.

Days are stepped by adding 24 hours and re-reading the zone rather than by
incrementing a date field, so a day that is 23 or 25 hours long still
produces the right sequence of `dateKey`s - the midday anchor is what keeps
a DST shift from landing the step on the previous or next day.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `anchor` | `Date` |
| `timeZone` | `string` |
| `count` | `number` |
| `now` | `Date` |

#### Returns

`DayColumn`[]

***

### Function: discountLabel() [#function-discountlabel]

```ts
function discountLabel(discount: AppliedDiscount): string;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `discount` | [`AppliedDiscount`](/docs/reference/core#interface-applieddiscount) |

#### Returns

`string`

***

### Function: filterNavForAudience() [#function-filternavforaudience]

```ts
function filterNavForAudience(nav: ConnectNav, isStaff: boolean): ConnectNav;
```

`nav` with every item the current session should not see removed - see
`ConnectNavItem.audience`.

Applied to nested `items` too, and a parent whose children all disappear is
dropped along with them: a section header pointing at nothing reads as a
broken link rather than as a hidden one.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `nav` | [`ConnectNav`](#type-alias-connectnav) |
| `isStaff` | `boolean` |

#### Returns

[`ConnectNav`](#type-alias-connectnav)

***

### Function: flattenNav() [#function-flattennav]

```ts
function flattenNav(nav: ConnectNav): ConnectNavItem[];
```

Every item in the tree, parents and children alike, in render order.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `nav` | [`ConnectNav`](#type-alias-connectnav) |

#### Returns

[`ConnectNavItem`](#interface-connectnavitem)[]

***

### Function: formatAiValue() [#function-formataivalue]

```ts
function formatAiValue(value: AiValue, format?: AiValueFormat): string;
```

#### Parameters

| Parameter | Type | Default value |
| ------ | ------ | ------ |
| `value` | [`AiValue`](/docs/reference/core#type-alias-aivalue) | `undefined` |
| `format` | [`AiValueFormat`](/docs/reference/core#type-alias-aivalueformat) | `"text"` |

#### Returns

`string`

***

### Function: formatPrice() [#function-formatprice]

```ts
function formatPrice(paise: number): string;
```

Paise are the wire unit everywhere in Connect; rupees are the display unit.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `paise` | `number` |

#### Returns

`string`

***

### Function: gateExemptPaths() [#function-gateexemptpaths]

```ts
function gateExemptPaths(nav: ConnectNav): string[];
```

Hrefs declared `gateExempt` - the routes `AppShell` renders outside its `ConnectAccessGate`.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `nav` | [`ConnectNav`](#type-alias-connectnav) |

#### Returns

`string`[]

***

### Function: groupPlansByTier() [#function-groupplansbytier]

```ts
function groupPlansByTier(plans: readonly Plan[]): PlanTier[];
```

Collapses a flat plan list into one card per tier.

This is the shape the grid wants and the old one didn't have. It used to
filter the whole list down to the toggled interval, which meant a tier sold
monthly-only simply vanished when a customer looked at annual pricing, and a
catalog with no monthly plans at all rendered an empty grid on first paint.
Grouping first means the toggle picks a *price inside a card*, and a card
with only one cadence still shows.

Ordered by the tier's cheapest monthly-equivalent price, so the grid reads
left-to-right cheapest-first regardless of what order the API returned.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `plans` | readonly [`Plan`](/docs/reference/core#interface-plan)[] |

#### Returns

[`PlanTier`](#interface-plantier)[]

***

### Function: isFreePlan() [#function-isfreeplan]

```ts
function isFreePlan(price: number, addonsTotal?: number): boolean;
```

Whether a plan costs nothing - the SDK's copy of Connect's `isFreeSelection`
(`packages/data/src/payments/free-plan.ts`), which is what the server
actually decides the free-activation path with.

A copy rather than an import for the reason `types.ts` gives: the published
package carries none of Connect's internals. The rule is one line and does
not drift, and `conformance.test.ts` pins the shape either side of it.

#### Parameters

| Parameter | Type | Default value | Description |
| ------ | ------ | ------ | ------ |
| `price` | `number` | `undefined` | - |
| `addonsTotal` | `number` | `0` | Paise of the add-ons *selected*, not of the plan's whole catalog. A free plan with a paid add-on ticked is not free. |

#### Returns

`boolean`

***

### Function: isLinkableConnectScreen() [#function-islinkableconnectscreen]

```ts
function isLinkableConnectScreen(screens: readonly ConnectScreen[], href: string): boolean;
```

Whether an href the model produced may be rendered as a link.

Same reasoning as Connect's own `showActions` check, and the same conclusion:
an assistant that can emit an arbitrary URL is a redirect the model can be
talked into, and retrieved content shares a context window with its
tool-calling authority. So a link is drawn only when it resolves to a screen
*your application declared* - which makes an external destination structurally
impossible, since no route pattern has a scheme or a host.

The protocol-relative form is refused separately: `//evil.example.com` has no
scheme for a path matcher to trip over, and would otherwise reach an anchor
as an absolute destination.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `screens` | readonly [`ConnectScreen`](/docs/reference/core#interface-connectscreen)[] |
| `href` | `string` |

#### Returns

`boolean`

***

### Function: isNavItemActive() [#function-isnavitemactive]

```ts
function isNavItemActive(item: ConnectNavItem, activeHref: string | null): boolean;
```

Whether `item` is the destination `resolveActivePath` picked.

Not just `normalizePath(item.href) === activeHref`, which is the trap this
exists to close: `normalizePath` strips the origin off an absolute URL, so
an external item pointed at `https://docs.example.com` normalizes to `/` and
lights up on every app's home route. External items are never active.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `item` | [`ConnectNavItem`](#interface-connectnavitem) |
| `activeHref` | `string` \| `null` |

#### Returns

`boolean`

***

### Function: isNumericAiFormat() [#function-isnumericaiformat]

```ts
function isNumericAiFormat(format?: AiValueFormat): boolean;
```

Numeric columns read far better right-aligned; everything else stays with the text.

#### Parameters

| Parameter | Type | Default value |
| ------ | ------ | ------ |
| `format` | [`AiValueFormat`](/docs/reference/core#type-alias-aivalueformat) | `"text"` |

#### Returns

`boolean`

***

### Function: layoutBookings() [#function-layoutbookings]

```ts
function layoutBookings(
   bookings: Booking[], 
   timeZone: string, 
   window: GridWindow): PositionedBooking[];
```

Places bookings on the grid, side by side where they collide.

Overlap handling is the part worth stating. Two appointments at the same
time are not an error - a workspace with two hosts has them constantly - and
stacking them would hide one entirely. So overlapping bookings are grouped
into *clusters* and each cluster is split into equal columns. The cluster is
built transitively: A overlapping B and B overlapping C puts all three in one
cluster even when A and C do not touch, because anything else lets two of
them land on the same column.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `bookings` | [`Booking`](/docs/reference/core#interface-booking)[] |
| `timeZone` | `string` |
| `window` | [`GridWindow`](#interface-gridwindow) |

#### Returns

[`PositionedBooking`](#interface-positionedbooking)[]

***

### Function: matchConnectScreen() [#function-matchconnectscreen]

```ts
function matchConnectScreen(screens: readonly ConnectScreen[], path: string): ConnectScreen | null;
```

The declared screen a live path is on, or `null` if none matches.

`null` is a normal outcome, not an error: an application need only describe
the screens it wants the assistant to know about, and an undescribed one
simply costs the assistant its orientation rather than costing the user an
answer.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `screens` | readonly [`ConnectScreen`](/docs/reference/core#interface-connectscreen)[] |
| `path` | `string` |

#### Returns

[`ConnectScreen`](/docs/reference/core#interface-connectscreen) \| `null`

***

### Function: monthlyOf() [#function-monthlyof]

```ts
function monthlyOf(price: number, billingInterval: BillingInterval): number;
```

An annual plan's price is 12 cycles of the same product, so any comparison
across billing intervals has to normalise first - otherwise every annual
plan reads as an upgrade over every monthly one.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `price` | `number` |
| `billingInterval` | [`BillingInterval`](/docs/reference/core#type-alias-billinginterval) |

#### Returns

`number`

***

### Function: normalizeNav() [#function-normalizenav]

```ts
function normalizeNav(nav: ConnectNav): ConnectNavSection[];
```

Folds a mixed `ConnectNav` into sections, gathering consecutive bare items into one untitled group.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `nav` | [`ConnectNav`](#type-alias-connectnav) |

#### Returns

[`ConnectNavSection`](#interface-connectnavsection)[]

***

### Function: normalizePath() [#function-normalizepath]

```ts
function normalizePath(path: string): string;
```

`https://x.test/billing/?tab=1#top` -> `/billing`.

Trailing slashes go too, so `/billing/` and `/billing` are the same route -
they are, to every router, and treating them as different is how a nav item
ends up unhighlighted on exactly one of the two URLs a user can arrive at.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `path` | `string` |

#### Returns

`string`

***

### Function: readVisitorStream() [#function-readvisitorstream]

```ts
function readVisitorStream(response: Response, handlers: VisitorStreamHandlers): Promise<void>;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `response` | `Response` |
| `handlers` | [`VisitorStreamHandlers`](#interface-visitorstreamhandlers) |

#### Returns

`Promise`\<`void`\>

***

### Function: resolveAction() [#function-resolveaction]

```ts
function resolveAction(plan: Plan, current: 
  | SubscriptionResponse
  | null): PlanAction;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `plan` | [`Plan`](/docs/reference/core#interface-plan) |
| `current` | \| [`SubscriptionResponse`](/docs/reference/core#interface-subscriptionresponse) \| `null` |

#### Returns

[`PlanAction`](#type-alias-planaction)

***

### Function: resolveActivePath() [#function-resolveactivepath]

```ts
function resolveActivePath(pathname: string, nav: ConnectNav): string | null;
```

The href of the nav item the current path belongs to, or `null`.

Longest prefix wins, so `/settings/billing` lights up Billing and not also
its parent Settings - a plain `startsWith` over an unordered list lights up
both, and the sidebar grows two highlights.

External items never match: they leave the app, so no path inside it is
ever "on" them.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `pathname` | `string` |
| `nav` | [`ConnectNav`](#type-alias-connectnav) |

#### Returns

`string` \| `null`

***

### Function: resolvePlanGroup() [#function-resolveplangroup]

```ts
function resolvePlanGroup(plan: ComparablePlan): string;
```

Falls back to a slug of the name for a Connect deployment predating
`planGroup` on the wire, mirroring `resolvePlanGroup` server-side. Kept so
an SDK upgrade doesn't require the partner's Connect instance to be
upgraded in lockstep - an older server simply omits the field and the grid
groups the way it always did.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `plan` | [`ComparablePlan`](#interface-comparableplan) |

#### Returns

`string`

***

### Function: resolveSubscriptionState() [#function-resolvesubscriptionstate]

```ts
function resolveSubscriptionState(subscription: 
  | SubscriptionResponse
  | null, isTrialing: boolean): ConnectSubscriptionState;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `subscription` | \| [`SubscriptionResponse`](/docs/reference/core#interface-subscriptionresponse) \| `null` |
| `isTrialing` | `boolean` |

#### Returns

[`ConnectSubscriptionState`](#type-alias-connectsubscriptionstate)

***

### Function: sidebarRowClass() [#function-sidebarrowclass]

```ts
function sidebarRowClass(options?: Readonly<{
  tile?: boolean;
  active?: boolean;
  className?: string;
}>): string;
```

The class list for one sidebar row, in one place.

Three different elements need it - a plain `<button>` (`ConnectSidebarRow`),
a rendered link (`ConnectSidebarNav`), and a HeroUI `<Button>` acting as a
`Modal`/`Dropdown` trigger (the account row, the launcher, the workspace
tile). They cannot share a component, because a React Aria overlay has to
own its own trigger element - so they share this instead. A second copy of
the string is a row that drifts off the 32px centre and takes the collapse
animation's smoothness with it.

On the HeroUI trigger: these are all Tailwind *utilities*, and HeroUI's
`.button` is a component class, so the utility layer wins - which is what
lets a `w-fit`, centred, `px-`-padded button take this shape at all.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `options?` | `Readonly`\<\{ `tile?`: `boolean`; `active?`: `boolean`; `className?`: `string`; \}\> |

#### Returns

`string`

***

### Function: useActivePath() [#function-useactivepath]

```ts
function useActivePath(activePath?: string): string;
```

The current path, from `activePath` when given and from the browser
otherwise.

Exported because a host building its own shell out of `ConnectSideNav`
needs the same fallback the shell uses.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `activePath?` | `string` |

#### Returns

`string`

***

### Function: useAvailabilitySchedules() [#function-useavailabilityschedules]

```ts
function useAvailabilitySchedules(): {
  schedules: AvailabilitySchedule[];
  isLoading: boolean;
  error: string | null;
};
```

The working-hours templates an appointment type can point at.

Read this before offering a "set up availability" step - a workspace that
already has one does not need a second "Working hours" from your UI.

#### Returns

```ts
{
  schedules: AvailabilitySchedule[];
  isLoading: boolean;
  error: string | null;
}
```

##### schedules

```ts
schedules: AvailabilitySchedule[];
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

***

### Function: useAvailableSlots() [#function-useavailableslots]

```ts
function useAvailableSlots(query: SlotQuery | null): {
  slots: BookingSlot[];
  timezone: string | null;
  durationMinutes: number | null;
  isLoading: boolean;
  error: string | null;
  refetch: () => void;
};
```

Free times for one appointment type.

**Deliberately re-read rather than cached across a session.** A slot list is
a snapshot of contention: anyone else on that calendar - a colleague, a
voice agent mid-call, someone on the public booking link - can take one
between the read and the click. Showing a stale one is how two people are
offered the same minute. The booking call re-checks inside a transaction
anyway, so a stale list costs a refusal rather than a double booking; this
just means the refusal is rare.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `query` | [`SlotQuery`](#interface-slotquery) \| `null` |

#### Returns

##### slots

```ts
slots: BookingSlot[];
```

##### timezone

```ts
timezone: string | null;
```

##### durationMinutes

```ts
durationMinutes: number | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch

```ts
refetch: () => void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useBookableEventTypes() [#function-usebookableeventtypes]

```ts
function useBookableEventTypes(): {
  eventTypes: BookableEventType[];
  isLoading: boolean;
  error: string | null;
};
```

What this workspace offers as bookable. Active types only.

#### Returns

```ts
{
  eventTypes: BookableEventType[];
  isLoading: boolean;
  error: string | null;
}
```

##### eventTypes

```ts
eventTypes: BookableEventType[];
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

***

### Function: useBookingActions() [#function-usebookingactions]

```ts
function useBookingActions(): BookingActionsState & {
  createBooking: Promise<Booking | null>;
  rescheduleBooking: Promise<Booking | null>;
  cancelBooking: Promise<Booking | null>;
};
```

Book, move and cancel.

**None of these is optimistic**, and that is not an oversight. Whether a
slot is still free is a question only the server can answer, inside the
transaction that decides it - two people can be holding the same minute
right now. Painting a booking as made and withdrawing it is exactly the lie
the two-phase hold exists to prevent.

#### Returns

`BookingActionsState` & \{
  `createBooking`: `Promise`\<[`Booking`](/docs/reference/core#interface-booking) \| `null`\>;
  `rescheduleBooking`: `Promise`\<[`Booking`](/docs/reference/core#interface-booking) \| `null`\>;
  `cancelBooking`: `Promise`\<[`Booking`](/docs/reference/core#interface-booking) \| `null`\>;
\}

***

### Function: useBookings() [#function-usebookings]

```ts
function useBookings(query: BookingsQuery): {
  bookings: Booking[];
  hasMore: boolean;
  isLoading: boolean;
  error: string | null;
  path: string;
  refetch: () => void;
};
```

One page of the diary, ascending - a forward view, not a history.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `query` | [`BookingsQuery`](#interface-bookingsquery) |

#### Returns

##### bookings

```ts
bookings: Booking[];
```

##### hasMore

```ts
hasMore: boolean;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### path

```ts
path: string;
```

##### refetch

```ts
refetch: () => void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnect() [#function-useconnect]

```ts
function useConnect(): ConnectContextValue;
```

Browser half of `@cosmoops/connect-sdk` - React components and hooks.

Holds no credential. Everything here talks to your own origin, where
`createConnectHandler` from `@cosmoops/connect-sdk/server` holds the API key
and reaches Connect.

Styling is HeroUI v3 semantic tokens only (`accent`, `muted`, `surface`,
`separator`, ...), never a hardcoded colour - so these components inherit
whatever theme the host application platform already configured, and one theme
change lands identically in all of them.

#### Returns

[`ConnectContextValue`](#interface-connectcontextvalue)

***

### Function: useConnectAccessGate() [#function-useconnectaccessgate]

```ts
function useConnectAccessGate(): ConnectAccessGateResult;
```

The single composed answer to "can this workspace use my app right now,
and if not, which of the four things is it": choose a plan, wait on
approval, resolve an overdue payment, or - a suspended workspace - none of
the above, because nothing short of a platform admin fixes that one.

Reuses the existing single sources of truth rather than re-deriving them:
`blockingBlockers()` (`helpers.ts`) for severity, the same readiness read
`ConnectReadinessGate`/`ConnectReadinessBanner` already use. `ConnectAccessGate`
is a priority ordering over the same data, not a second opinion about it.

Priority, first match wins: `suspended` > `audience_mismatch` >
`no_subscription` > `onboarding_pending` > any other blocking code
(`blocked`) > `payment_retry` (advisory - `children` still render) >
`clear`.

`audience_mismatch` outranks `no_subscription` deliberately, and the
ordering is the whole point of it: a workspace of the wrong account type
carries BOTH blockers at once, and showing the subscription one first sent
the customer to a plan picker whose subscribe call
(`ActivateSubscriptionUseCase`) rejects them for the audience anyway. The
account type is the reason; the missing subscription is a consequence.

#### Returns

[`ConnectAccessGateResult`](#interface-connectaccessgateresult)

***

### Function: useConnectAddonUsage() [#function-useconnectaddonusage]

```ts
function useConnectAddonUsage(): {
  data:   | AddonUsageResponse
     | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

What is left of every consumable add-on on this workspace's subscription.

The read half of `client.billing.consumeAddon` - an application that meters its
own end users had no way to show them what remained until this existed.
Empty `addons` means there is nothing to meter, which is not an error.

#### Returns

##### data

```ts
data: 
  | AddonUsageResponse
  | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectApps() [#function-useconnectapps]

```ts
function useConnectApps(): {
  data:   | {
     apps: WorkspaceApp[];
   }
     | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

The apps this workspace can open.

#### Returns

##### data

```ts
data: 
  | {
  apps: WorkspaceApp[];
}
  | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectAudience() [#function-useconnectaudience]

```ts
function useConnectAudience(): SessionAudience | null;
```

Whether this account is the kind your application is sold to - `null` while
the session is loading or signed out.

`audience.matches === false` is a hard block, not a hint: readiness reports
`audience_mismatch` and subscribing is refused. `<ConnectAccessGate>`
already blocks the shell on it, so reach for this only when you need the
verdict somewhere the gate does not run - a marketing page, say, or a
sign-up CTA you would rather not show to an account that cannot complete it.

#### Returns

[`SessionAudience`](/docs/reference/core#interface-sessionaudience) \| `null`

***

### Function: useConnectCall() [#function-useconnectcall]

```ts
function useConnectCall(): ConnectCallContextValue;
```

Start a call yourself. Must be inside `<ConnectCallProvider>`.

#### Returns

[`ConnectCallContextValue`](#interface-connectcallcontextvalue)

***

### Function: useConnectCheckout() [#function-useconnectcheckout]

```ts
function useConnectCheckout(): ConnectCheckoutState & {
  start: void;
  reset: void;
};
```

Drives a checkout that completes on Connect, not here.

The partner's page cannot complete a subscription itself - see
`ConnectPlans`' doc comment for why that is deliberate - so the flow is:
open Connect, watch `/entitlements` until the entitlement changes, then
refresh the session so every Connect component on the page repaints.

**The result is read from Connect's own state, never from the window.**
A closed window proves nothing: the customer may have paid and closed it,
or opened it, thought better of it, and closed it. Polling is what tells
the two apart, which is why the window closing only starts a short grace
period rather than ending the run.

#### Returns

***

### Function: useConnectCompanyProfile() [#function-useconnectcompanyprofile]

```ts
function useConnectCompanyProfile(): {
  data:   | CompanyProfileResponse
     | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

The platform's legal identity and DPDP grievance channel.

The one read here that does not depend on a session, so it resolves the
same for a signed-out visitor - which is deliberate, because the moment a
grievance channel most needs to be readable is on a public privacy page.

Read it rather than copying it into your own configuration; see
`CompanyProfile`. `<ConnectGrievanceNotice>` renders it for you.

#### Returns

##### data

```ts
data: 
  | CompanyProfileResponse
  | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectEntitlements() [#function-useconnectentitlements]

```ts
function useConnectEntitlements(): {
  data:   | {
     readiness: ReadinessResponse;
     subscription: SubscriptionResponse;
   }
     | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

Readiness gates and the live subscription, fetched on first use.

#### Returns

##### data

```ts
data: 
  | {
  readiness: ReadinessResponse;
  subscription: SubscriptionResponse;
}
  | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectFeature() [#function-useconnectfeature]

```ts
function useConnectFeature(feature: string): {
  isEntitled: boolean;
  isLoading: boolean;
  features: string[];
};
```

Whether the live subscription unlocks a plan feature.

The browser twin of `client.entitlements.hasFeature` on the server, and the
same posture: `false` rather than a throw when there is no subscription at
all, because "not entitled" is the same answer either way and a feature check
should not need a try/catch around it.

`isLoading` is returned separately and matters: `false` while the first read
is in flight means "we don't know yet", not "no". Gate on `isEntitled` for
what to render and on `isLoading` for whether to render anything at all -
`ConnectFeatureGate` does exactly that.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `feature` | `string` |

#### Returns

##### isEntitled

```ts
isEntitled: boolean;
```

##### isLoading

```ts
isLoading: boolean;
```

##### features

```ts
features: string[];
```

Every feature key the current plan unlocks - for a settings page listing what's included.

***

### Function: useConnectIntegrations() [#function-useconnectintegrations]

```ts
function useConnectIntegrations(): {
  data:   | IntegrationsListResponse
     | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

Enabled Composio-hosted toolkits and this workspace's connection status for each.

#### Returns

##### data

```ts
data: 
  | IntegrationsListResponse
  | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectOptional() [#function-useconnectoptional]

```ts
function useConnectOptional(): ConnectContextValue | null;
```

The context when there is one, `null` when there is not.

**For a component that must work on a page with no provider**, which is a
real case rather than defensive coding: `<ConnectVisitorChat>` goes on a
public marketing page, and those pages frequently have no `ConnectProvider`
anywhere near them - there is no session to hold and nothing on the page that
needs one. `useConnect()` throwing is right for every component that reads a
session; it is wrong for one whose whole purpose is the visitor who has none.

What inheriting the context buys such a component, when it is inside one, is
`basePath` and the injected `fetch` - so the same component renders against
Storybook's mock bridge and the docs site's canned fetch without either of
them having to pass anything.

Do not reach for this to make an ordinary component tolerant of a missing
provider. A session-reading component that silently renders a signed-out
state because somebody forgot to wrap their app is a bug that looks like a
product decision.

#### Returns

[`ConnectContextValue`](#interface-connectcontextvalue) \| `null`

***

### Function: useConnectPlans() [#function-useconnectplans]

```ts
function useConnectPlans(): {
  data: PlansResponse | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

This application's plan catalog.

#### Returns

##### data

```ts
data: PlansResponse | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectPlatformAccess() [#function-useconnectplatformaccess]

```ts
function useConnectPlatformAccess(): {
  isStaff: boolean;
  roles: string[];
  isLoading: boolean;
};
```

Whether this session is one of Connect's own operators - see
`SessionAccess.staff`.

`isStaff` is the branch you want; `roles` narrows further when your operator
console has tiers of its own (a `developer` seeing a read-only view of what
a `platform_admin` can edit, say).

**A rendering decision, not an access decision.** The browser can call this
whatever it likes. Everything an operator console exposes must be re-checked
server-side against `staff` from
`client.identity.getUser(sessionToken)` - see `<ConnectPlatformGate>`, which
says the same thing at more length.

#### Returns

##### isStaff

```ts
isStaff: boolean;
```

##### roles

```ts
roles: string[];
```

The staff role, as a one-element array.

Still an array because `<ConnectPlatformGate accept={[...]}>` matches
against a set, and because a session reports one role since
`role`/`permissions`/`staff` replaced the old `platformAccess.roles`
list - Connect's platform roles nest, so the highest one is the whole
answer. Empty for a member session.

##### isLoading

```ts
isLoading: boolean;
```

`true` while the session read is in flight - neither staff nor not-staff yet.

***

### Function: useConnectPortalUrl() [#function-useconnectportalurl]

```ts
function useConnectPortalUrl(path: string): string;
```

`connectPortalUrl`, with `appUrl`/`clientId` read from the provider instead
of threaded through by every caller.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `path` | `string` |

#### Returns

`string`

***

### Function: useConnectPush() [#function-useconnectpush]

```ts
function useConnectPush(serviceWorkerUrl?: string): UseConnectPush;
```

Register this browser for the platform's notifications.

The subscription is **Connect's, not your application's**: a member who
turns notifications on in one app has turned them on for the platform, and
the same device receives every other app's notifications and Connect's own.
That is the point of centralising it - one browser prompt instead of one per
app, and one place to turn everything off.

Your application needs a service worker at its own origin that handles the
`push` event; the SDK does not install one, because a worker is a
site-wide concern with caching implications only the host can decide.

#### Parameters

| Parameter | Type | Default value | Description |
| ------ | ------ | ------ | ------ |
| `serviceWorkerUrl` | `string` | `"/sw.js"` | Where your worker lives. Root-scoped by convention. |

#### Returns

[`UseConnectPush`](#interface-useconnectpush)

***

### Function: useConnectResource() [#function-useconnectresource]

```ts
function useConnectResource<T>(path: string, options?: Readonly<UseConnectResourceOptions>): {
  data: T | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

One bridge read, shared through the provider's cache.

Waits for a session by default: every resource route but one needs it, so
firing before one exists just buys a guaranteed 401. `requiresSession:
false` opts out, and the reason it exists is the grievance notice - a
statutory contact is at its most useful on a public page a signed-out
visitor is reading, so a hook that answered `null` until someone logged in
would render nothing at exactly the moment that matters.

#### Type Parameters

| Type Parameter |
| ------ |
| `T` |

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `path` | `string` |
| `options?` | `Readonly`\<[`UseConnectResourceOptions`](#interface-useconnectresourceoptions)\> |

#### Returns

##### data

```ts
data: T | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectSessions() [#function-useconnectsessions]

```ts
function useConnectSessions(): {
  data:   | SessionsListResponse
     | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

This Connect account's own login sessions - account-wide, not scoped to
this application, see `SessionsListResponse`'s doc comment. The component
that calls this is also the one that must own any revoke actions and call
the returned `refetch()` afterward - see `mutateResource`'s doc comment
for why nothing does that automatically.

#### Returns

##### data

```ts
data: 
  | SessionsListResponse
  | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectShell() [#function-useconnectshell]

```ts
function useConnectShell(): ConnectShellContextValue;
```

The shell state a sidebar part is rendering inside.

Falls back to "expanded, not collapsible, plain anchors" rather than
throwing, because every part is also exported for use *outside* a shell -
a host dropping `ConnectSidebarNav` into a layout it already owns should
not have to mount a provider to get a nav column.

#### Returns

[`ConnectShellContextValue`](#interface-connectshellcontextvalue)

***

### Function: useConnectUser() [#function-useconnectuser]

```ts
function useConnectUser(): ConnectUser | null;
```

The signed-in user, or `null`. Sugar over `useConnect().session`.

#### Returns

[`ConnectUser`](/docs/reference/core#interface-connectuser) \| `null`

***

### Function: useConnectWorkspaceMembers() [#function-useconnectworkspacemembers]

```ts
function useConnectWorkspaceMembers(): {
  data:   | {
     members: ConnectWorkspaceMember[];
   }
     | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

The team of the workspace this session is on, with each member's permissions
already resolved for this application.

#### Returns

##### data

```ts
data: 
  | {
  members: ConnectWorkspaceMember[];
}
  | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectWorkspaces() [#function-useconnectworkspaces]

```ts
function useConnectWorkspaces(): {
  data:   | {
     workspaces: UserWorkspace[];
   }
     | null;
  isLoading: boolean;
  error: string | null;
  refetch: void;
};
```

The workspaces this user belongs to - what `<ConnectWorkspaceSwitcher>`
renders.

Empty or single-entry results are normal, not errors: most accounts have one
workspace, and the switcher decides for itself whether that is worth
drawing.

#### Returns

##### data

```ts
data: 
  | {
  workspaces: UserWorkspace[];
}
  | null;
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch()

```ts
refetch(): void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useConnectWorkspaceType() [#function-useconnectworkspacetype]

```ts
function useConnectWorkspaceType(): WorkspaceType | null;
```

Whether the signed-in workspace is a one-seat personal account or a team -
`null` while the session is loading or signed out.

Branch on this to stop rendering team UI at a solo account: a members
table, an invite button and a seat meter are all dead controls on an
`"individual"` workspace, which has exactly one seat and cannot gain
another. `<ConnectWorkspaceTypeGate>` is the declarative form.

#### Returns

[`WorkspaceType`](/docs/reference/core#type-alias-workspacetype) \| `null`

***

### Function: useCreateAvailabilitySchedule() [#function-usecreateavailabilityschedule]

```ts
function useCreateAvailabilitySchedule(): BookingActionsState & {
  createSchedule: Promise<
     | AvailabilitySchedule
    | null>;
};
```

Gives a workspace its first working hours.

**Not optimistic** - same posture as `useBookingActions`: this is one of
the two writes a host's first-run setup makes, and a modal that closes on
a failed create is worse than one that stays open with the reason shown.

#### Returns

`BookingActionsState` & \{
  `createSchedule`: `Promise`\<
     \| [`AvailabilitySchedule`](/docs/reference/core#interface-availabilityschedule)
    \| `null`\>;
\}

***

### Function: useCreateEventType() [#function-usecreateeventtype]

```ts
function useCreateEventType(): BookingActionsState & {
  createEventType: Promise<
     | ManageableEventType
    | null>;
};
```

Creates the first thing a workspace can be booked for.

Host is a specific member, never a team - there is no groups read in this
bridge, so a round-robin team host stays a Console-only capability
reachable by following "Manage in Connect".

#### Returns

`BookingActionsState` & \{
  `createEventType`: `Promise`\<
     \| [`ManageableEventType`](/docs/reference/core#interface-manageableeventtype)
    \| `null`\>;
\}

***

### Function: useCreateWorkspace() [#function-usecreateworkspace]

```ts
function useCreateWorkspace(): WorkspaceActionsState & {
  createWorkspace: Promise<UserWorkspace | null>;
};
```

Opens another workspace under the tenant the signed-in user already owns.

**Not optimistic**, same posture as every write in `use-scheduling.ts`:
whether the tenant's plan still has room for another workspace is a
question only the server can answer, and a modal that closes before that
answer comes back is the lie a two-phase write exists to avoid.

The result is a `UserWorkspace` row with `isActive`/`subscribed` both
`false` - creating does not switch the session to it, and the workspace has
no subscription yet. Pair with `<ConnectWorkspaceSwitcher>`'s own
`mutateResource("/workspaces/switch", ...)` call to move into it, guarded
on `inAudience` the same way that component's `selectable()` already is.

#### Returns

[`WorkspaceActionsState`](#interface-workspaceactionsstate) & \{
  `createWorkspace`: `Promise`\<[`UserWorkspace`](/docs/reference/core#interface-userworkspace) \| `null`\>;
\}

***

### Function: useManageableEventTypes() [#function-usemanageableeventtypes]

```ts
function useManageableEventTypes(): {
  eventTypes: ManageableEventType[];
  isLoading: boolean;
  error: string | null;
  refetch: () => void;
};
```

The richer read `ConnectEventTypeFormModal`'s edit mode needs - everything
`useBookableEventTypes`'s booking catalogue deliberately withholds. See
the bridge route's own doc comment for why this is a separate read rather
than a wider response on that one. Includes inactive rows.

#### Returns

##### eventTypes

```ts
eventTypes: ManageableEventType[];
```

##### isLoading

```ts
isLoading: boolean;
```

##### error

```ts
error: string | null;
```

##### refetch

```ts
refetch: () => void;
```

Forces a fresh read - evicts this path's cache entry and re-runs the
read.

It used only to re-trigger the effect, on the assumption that the caller
had just evicted the entry itself through `mutateResource`. That made it
useless for the other reason to refetch: **something changed somewhere
this tab cannot see.** The provider's cache is a `Map` of resolved
promises with no expiry, so a `refetch()` without the eviction re-read
the same settled promise and reported the same stale answer forever -
which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
whose workspace had subscribed in a different one.

Still safe after a `mutateResource` against the same path: evicting an
entry that is already gone is a no-op.

###### Returns

`void`

***

### Function: useUpdateAvailabilitySchedule() [#function-useupdateavailabilityschedule]

```ts
function useUpdateAvailabilitySchedule(): BookingActionsState & {
  updateSchedule: Promise<
     | AvailabilitySchedule
    | null>;
};
```

Edits an existing availability schedule. Every field optional - send only what changed.

#### Returns

`BookingActionsState` & \{
  `updateSchedule`: `Promise`\<
     \| [`AvailabilitySchedule`](/docs/reference/core#interface-availabilityschedule)
    \| `null`\>;
\}

***

### Function: useUpdateEventType() [#function-useupdateeventtype]

```ts
function useUpdateEventType(): BookingActionsState & {
  updateEventType: Promise<
     | ManageableEventType
    | null>;
};
```

Edits an existing appointment type. Every field optional - send only what
changed. `hostMemberId` omitted leaves the host untouched, including a
*group* host this surface cannot represent.

#### Returns

`BookingActionsState` & \{
  `updateEventType`: `Promise`\<
     \| [`ManageableEventType`](/docs/reference/core#interface-manageableeventtype)
    \| `null`\>;
\}

***

### Function: visitorSlotOfferFrom() [#function-visitorslotofferfrom]

```ts
function visitorSlotOfferFrom(toolCalls: readonly VisitorToolCall[]): VisitorSlotOffer | null;
```

The times a turn looked up, lifted out of its tool calls.

**This is what the buttons are drawn from, never the assistant's sentence.** A
time the visitor taps is then one the server offered, which is what makes it
impossible to book an appointment at a time nobody gave - the property
`/visitor/book` depends on and cannot check for itself.

`null` when the turn looked nothing up, and also when the lookup *refused* -
a tool answering `ok: false` is the assistant being told there is nothing
available, and it says so in prose. Rendering an empty slot list under that
would be a second, emptier way of saying the same thing.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `toolCalls` | readonly [`VisitorToolCall`](/docs/reference/core#interface-visitortoolcall)[] |

#### Returns

[`VisitorSlotOffer`](/docs/reference/core#interface-visitorslotoffer) \| `null`

***

### Function: zonedInstant() [#function-zonedinstant]

```ts
function zonedInstant(instant: Date, timeZone: string): ZonedInstant;
```

An instant, as the local day and minute it lands on in `timeZone`.

Built from `formatToParts` rather than by adding a fixed offset. An offset
is only correct until a DST boundary, and "add 5.5 hours" is wrong the
moment the same code is pointed at a zone that observes one - which is
exactly the kind of bug that appears twice a year and never in a test.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `instant` | `Date` |
| `timeZone` | `string` |

#### Returns

`ZonedInstant`