@cosmoops/connect-sdk 0.7.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +662 -0
- package/README.md +376 -124
- package/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +59 -5
- package/dist/cjs/client/components/ai-format.js +135 -0
- package/dist/cjs/client/components/ai-widgets.js +232 -0
- package/dist/cjs/client/components/app-shell.js +80 -45
- package/dist/cjs/client/components/apps-grid.js +3 -2
- package/dist/cjs/client/components/ask-ai.js +353 -0
- package/dist/cjs/client/components/confirm-dialog.js +40 -0
- package/dist/cjs/client/components/cursor-pagination.js +22 -0
- package/dist/cjs/client/components/filter-tabs.js +24 -0
- package/dist/cjs/client/components/grievance-notice.js +30 -0
- package/dist/cjs/client/components/link-button.js +17 -10
- package/dist/cjs/client/components/logo.js +4 -3
- package/dist/cjs/client/components/nav.js +42 -1
- package/dist/cjs/client/components/plan-pricing.js +27 -0
- package/dist/cjs/client/components/plans.js +1 -1
- package/dist/cjs/client/components/platform-gate.js +48 -0
- package/dist/cjs/client/components/shell-context.js +149 -0
- package/dist/cjs/client/components/side-nav.js +50 -24
- package/dist/cjs/client/components/sidebar.js +127 -0
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +1 -1
- package/dist/cjs/client/components/user-menu.js +122 -40
- package/dist/cjs/client/components/workspace-switcher.js +100 -0
- package/dist/cjs/client/components/workspace-type-gate.js +48 -0
- package/dist/cjs/client/index.js +98 -3
- package/dist/cjs/client/provider.js +100 -2
- package/dist/cjs/client/use-checkout.js +9 -5
- package/dist/cjs/errors.js +1 -1
- package/dist/cjs/index.js +11 -1
- package/dist/cjs/next/index.js +2 -2
- package/dist/cjs/server/client.js +25 -2
- package/dist/cjs/server/handler.js +237 -16
- package/dist/cjs/server/http.js +52 -26
- package/dist/cjs/server/resources/acting-as.js +16 -0
- package/dist/cjs/server/resources/ai.js +15 -2
- package/dist/cjs/server/resources/data.js +374 -0
- package/dist/cjs/server/resources/entitlements.js +41 -0
- package/dist/cjs/server/resources/identity.js +9 -3
- package/dist/cjs/server/resources/kv.js +84 -0
- package/dist/cjs/server/resources/platform.js +43 -0
- package/dist/cjs/server/resources/sessions.js +1 -1
- package/dist/cjs/server/resources/workspaces.js +74 -0
- package/dist/cjs/store.js +152 -0
- package/dist/cjs/ui/empty-state.js +28 -0
- package/dist/cjs/ui/index.js +44 -0
- package/dist/cjs/ui/page-header.js +27 -0
- package/dist/cjs/ui/page-shell.js +44 -0
- package/dist/cjs/ui/section.js +20 -0
- package/dist/cjs/ui/setting-row.js +14 -0
- package/dist/cjs/ui/status-chip.js +17 -0
- package/dist/esm/cli/doctor.js +3 -3
- package/dist/esm/cli/init.js +3 -3
- package/dist/esm/client/components/access-gate.js +60 -6
- package/dist/esm/client/components/ai-format.js +129 -0
- package/dist/esm/client/components/ai-widgets.js +223 -0
- package/dist/esm/client/components/app-shell.js +85 -50
- package/dist/esm/client/components/apps-grid.js +3 -2
- package/dist/esm/client/components/ask-ai.js +350 -0
- package/dist/esm/client/components/confirm-dialog.js +37 -0
- package/dist/esm/client/components/cursor-pagination.js +19 -0
- package/dist/esm/client/components/filter-tabs.js +21 -0
- package/dist/esm/client/components/grievance-notice.js +27 -0
- package/dist/esm/client/components/link-button.js +17 -10
- package/dist/esm/client/components/logo.js +4 -3
- package/dist/esm/client/components/nav.js +41 -1
- package/dist/esm/client/components/plan-pricing.js +25 -0
- package/dist/esm/client/components/plans.js +2 -2
- package/dist/esm/client/components/platform-gate.js +45 -0
- package/dist/esm/client/components/shell-context.js +141 -0
- package/dist/esm/client/components/side-nav.js +49 -22
- package/dist/esm/client/components/sidebar.js +120 -0
- package/dist/esm/client/components/subscribe-dialog.js +3 -3
- package/dist/esm/client/components/subscription.js +2 -2
- package/dist/esm/client/components/user-menu.js +121 -41
- package/dist/esm/client/components/workspace-switcher.js +97 -0
- package/dist/esm/client/components/workspace-type-gate.js +45 -0
- package/dist/esm/client/index.js +58 -5
- package/dist/esm/client/provider.js +94 -2
- package/dist/esm/client/use-checkout.js +9 -5
- package/dist/esm/errors.js +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/next/index.js +2 -2
- package/dist/esm/server/client.js +25 -2
- package/dist/esm/server/handler.js +237 -16
- package/dist/esm/server/http.js +52 -26
- package/dist/esm/server/resources/acting-as.js +13 -0
- package/dist/esm/server/resources/ai.js +15 -2
- package/dist/esm/server/resources/data.js +369 -0
- package/dist/esm/server/resources/entitlements.js +41 -0
- package/dist/esm/server/resources/identity.js +9 -3
- package/dist/esm/server/resources/kv.js +80 -0
- package/dist/esm/server/resources/platform.js +39 -0
- package/dist/esm/server/resources/sessions.js +1 -1
- package/dist/esm/server/resources/workspaces.js +70 -0
- package/dist/esm/store.js +141 -0
- package/dist/esm/ui/empty-state.js +25 -0
- package/dist/esm/ui/index.js +34 -0
- package/dist/esm/ui/page-header.js +24 -0
- package/dist/esm/ui/page-shell.js +41 -0
- package/dist/esm/ui/section.js +17 -0
- package/dist/esm/ui/setting-row.js +11 -0
- package/dist/esm/ui/status-chip.js +14 -0
- package/dist/theme.css +87 -0
- package/dist/types/client/components/access-gate.d.ts +17 -3
- package/dist/types/client/components/ai-format.d.ts +24 -0
- package/dist/types/client/components/ai-widgets.d.ts +42 -0
- package/dist/types/client/components/app-shell.d.ts +85 -31
- package/dist/types/client/components/apps-grid.d.ts +11 -2
- package/dist/types/client/components/ask-ai.d.ts +73 -0
- package/dist/types/client/components/confirm-dialog.d.ts +34 -0
- package/dist/types/client/components/cursor-pagination.d.ts +27 -0
- package/dist/types/client/components/filter-tabs.d.ts +26 -0
- package/dist/types/client/components/grievance-notice.d.ts +31 -0
- package/dist/types/client/components/link-button.d.ts +32 -9
- package/dist/types/client/components/logo.d.ts +18 -2
- package/dist/types/client/components/nav.d.ts +27 -3
- package/dist/types/client/components/plan-pricing.d.ts +10 -0
- package/dist/types/client/components/platform-gate.d.ts +53 -0
- package/dist/types/client/components/shell-context.d.ts +116 -0
- package/dist/types/client/components/side-nav.d.ts +28 -14
- package/dist/types/client/components/sidebar.d.ts +95 -0
- package/dist/types/client/components/user-menu.d.ts +70 -23
- package/dist/types/client/components/workspace-switcher.d.ts +49 -0
- package/dist/types/client/components/workspace-type-gate.d.ts +48 -0
- package/dist/types/client/index.d.ts +36 -7
- package/dist/types/client/provider.d.ts +149 -3
- package/dist/types/client/use-checkout.d.ts +8 -4
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/next/index.d.ts +1 -1
- package/dist/types/server/client.d.ts +25 -2
- package/dist/types/server/http.d.ts +14 -3
- package/dist/types/server/index.d.ts +4 -0
- package/dist/types/server/resources/acting-as.d.ts +11 -0
- package/dist/types/server/resources/ai.d.ts +9 -1
- package/dist/types/server/resources/data.d.ts +258 -0
- package/dist/types/server/resources/entitlements.d.ts +33 -1
- package/dist/types/server/resources/kv.d.ts +63 -0
- package/dist/types/server/resources/platform.d.ts +32 -0
- package/dist/types/server/resources/workspaces.d.ts +55 -0
- package/dist/types/store.d.ts +96 -0
- package/dist/types/types.d.ts +491 -13
- package/dist/types/ui/empty-state.d.ts +34 -0
- package/dist/types/ui/index.d.ts +40 -0
- package/dist/types/ui/page-header.d.ts +34 -0
- package/dist/types/ui/page-shell.d.ts +39 -0
- package/dist/types/ui/section.d.ts +26 -0
- package/dist/types/ui/setting-row.d.ts +18 -0
- package/dist/types/ui/status-chip.d.ts +36 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# @cosmoops/connect-sdk
|
|
2
2
|
|
|
3
|
-
Official client for the **CosmoOps Connect
|
|
3
|
+
Official client for the **CosmoOps Connect application-partner API** (`/api/v1/application/*`).
|
|
4
|
+
|
|
5
|
+
**Current version: `0.8.0`** · [Release notes](#release-notes) · [Full changelog](./CHANGELOG.md)
|
|
6
|
+
— follows semver with the 0.x convention that a **minor** version may carry a
|
|
7
|
+
breaking change (a patch never does). Read the release notes for your target
|
|
8
|
+
version before upgrading past a minor bump.
|
|
4
9
|
|
|
5
10
|
```bash
|
|
6
11
|
npm install @cosmoops/connect-sdk
|
|
12
|
+
npm install streamdown # optional — Ask AI's markdown renderer, see step 4
|
|
7
13
|
```
|
|
8
14
|
|
|
9
15
|
Two entry points, and the split is a security boundary, not a preference:
|
|
@@ -23,9 +29,114 @@ Browser ──► your backend ──► Connect
|
|
|
23
29
|
(client) (createConnectHandler + server client, holds the key)
|
|
24
30
|
```
|
|
25
31
|
|
|
32
|
+
## Quickstart (Next.js)
|
|
33
|
+
|
|
34
|
+
The fast path — most integrations are this and nothing else. Not on Next.js?
|
|
35
|
+
Skip to [Other frameworks](#other-frameworks); the shape is the same, you
|
|
36
|
+
just wire the session cookie yourself.
|
|
37
|
+
|
|
38
|
+
**1. Install and get credentials.** From your application's Connect client section
|
|
39
|
+
in Connect, copy the API key and OAuth client id.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @cosmoops/connect-sdk
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**2. Set environment variables.**
|
|
46
|
+
|
|
47
|
+
| Variable | Required | Purpose |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `CONNECT_API_KEY` | yes | server-side credential |
|
|
50
|
+
| `CONNECT_CLIENT_ID` | yes | OAuth client id |
|
|
51
|
+
| `CONNECT_APP_URL` | no | non-production Connect instance |
|
|
52
|
+
| `CONNECT_SESSION_COOKIE` | no | session cookie name |
|
|
53
|
+
|
|
54
|
+
**3. Mount the bridge route.** One file, no arguments — this is your entire backend integration:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// app/api/connect/[...connect]/route.ts
|
|
58
|
+
export { GET, POST } from "@cosmoops/connect-sdk/next";
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**4. Wrap your layout and import the theme.**
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// app/layout.tsx
|
|
65
|
+
import { ConnectProvider } from "@cosmoops/connect-sdk/client";
|
|
66
|
+
|
|
67
|
+
<ConnectProvider>{children}</ConnectProvider>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
/* app/globals.css */
|
|
72
|
+
@import "tailwindcss";
|
|
73
|
+
@import "@heroui/styles";
|
|
74
|
+
@import "@cosmoops/connect-sdk/theme.css";
|
|
75
|
+
|
|
76
|
+
/* Ask AI's markdown renderer. Tailwind skips node_modules unless told, and
|
|
77
|
+
Streamdown's utility classes live inside its compiled bundle. Adjust the
|
|
78
|
+
`../` count to reach your own node_modules. */
|
|
79
|
+
@source "../node_modules/streamdown/dist/*.js";
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// app/layout.tsx — Streamdown's streaming animation keyframes
|
|
84
|
+
import "streamdown/styles.css";
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Skip the last two if you pass `askAi={false}` to `AppShell`. The colour tokens
|
|
88
|
+
Streamdown expects are already mapped onto HeroUI's in `theme.css`.
|
|
89
|
+
|
|
90
|
+
**5. Register the redirect URI.** In the application's Connect client section, add
|
|
91
|
+
`{your origin}/api/connect/callback`. This is the one step the SDK cannot do
|
|
92
|
+
for you.
|
|
93
|
+
|
|
94
|
+
**6. Read the session wherever you need it.**
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// app/dashboard/page.tsx
|
|
98
|
+
import { requireConnectSession } from "@cosmoops/connect-sdk/next";
|
|
99
|
+
|
|
100
|
+
export default async function DashboardPage() {
|
|
101
|
+
const session = await requireConnectSession(); // UserResponse — redirects if signed out
|
|
102
|
+
return <h1>{session.user.workspaceName}</h1>;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**7. Verify the wiring.**
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx @cosmoops/connect-sdk doctor
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
That's the whole round trip — `/signin`, `/callback`, an httpOnly session
|
|
113
|
+
cookie, `/session`, `/entitlements`, `/plans`, `/addons`, `/apps`,
|
|
114
|
+
`/sessions`, `/logout` — with no `next.config.ts` changes, including
|
|
115
|
+
`transpilePackages`: `dist/` is already ES2022 with `"use client"` hoisted,
|
|
116
|
+
so Turbopack and webpack both consume it as-is.
|
|
117
|
+
|
|
118
|
+
`npx @cosmoops/connect-sdk init` writes step 3's route file for you, plus a
|
|
119
|
+
`/plans` page and an `.env.local` template for step 2 — then prints steps 4
|
|
120
|
+
and 5 as the ones it won't do on your behalf, since those touch your own
|
|
121
|
+
layout and Connect's dashboard rather than files it can safely write. See
|
|
122
|
+
[Scaffolding](#scaffolding) below.
|
|
123
|
+
|
|
124
|
+
Two things most integrations add next:
|
|
125
|
+
|
|
126
|
+
- **A shell** — `AppShell` from `/client` is the rail and the access
|
|
127
|
+
gate every application platform needs. See [App shell](#app-shell).
|
|
128
|
+
- **Route protection before render** — see [Route protection and
|
|
129
|
+
`proxy.ts`](#route-protection-and-proxyts) below; `requireConnectSession()`
|
|
130
|
+
above already redirects a signed-out visitor, so you only need this for
|
|
131
|
+
visitors you want bounced *before* a page starts rendering.
|
|
132
|
+
|
|
133
|
+
The sections below this point cover the manual path — rolling the OAuth round
|
|
134
|
+
trip yourself for a non-Next backend, or understanding what `/next` is doing
|
|
135
|
+
for you under the hood.
|
|
136
|
+
|
|
26
137
|
## Server setup
|
|
27
138
|
|
|
28
|
-
One credential does three jobs — the API key plaintext from your
|
|
139
|
+
One credential does three jobs — the API key plaintext from your application's Connect client section is also your OAuth2 `client_secret` and the key your `id_token` is signed with. Revoking it revokes all three.
|
|
29
140
|
|
|
30
141
|
```ts
|
|
31
142
|
import { ConnectClient } from "@cosmoops/connect-sdk/server";
|
|
@@ -81,6 +192,49 @@ const { user, serviceRole } = await connect.identity.getUser(sessionToken);
|
|
|
81
192
|
if (!serviceRole.granted.includes("invoice:write")) return forbidden();
|
|
82
193
|
```
|
|
83
194
|
|
|
195
|
+
## Know which kind of account signed in
|
|
196
|
+
|
|
197
|
+
`user.workspaceType` is `"individual"` (a one-seat personal account) or
|
|
198
|
+
`"business"` (a team). It is the one fact about the workspace you cannot look
|
|
199
|
+
up yourself — this API has no workspace read — and it decides whether half
|
|
200
|
+
your UI is real: an individual workspace has exactly one seat and can never
|
|
201
|
+
gain another, so a members table, an invite button and a seat meter can only
|
|
202
|
+
ever fail there.
|
|
203
|
+
|
|
204
|
+
`audience` answers the other half — what Connect's catalogue says your
|
|
205
|
+
application is *sold* to, and whether this account qualifies:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const { user, audience } = await connect.identity.getUser(sessionToken);
|
|
209
|
+
|
|
210
|
+
user.workspaceType; // "individual"
|
|
211
|
+
audience.application; // "b2b"
|
|
212
|
+
audience.matches; // false — readiness will block, subscribing is refused
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Both ship together so the check stays `!audience.matches` rather than a
|
|
216
|
+
hardcoded audience constant in your own code that goes stale the moment an
|
|
217
|
+
operator changes the catalogue entry. `audience.enforcedAtSignIn` says whether
|
|
218
|
+
Connect refuses the hand-off itself; when it does, a mismatch only ever reaches
|
|
219
|
+
you on a session minted before that setting was turned on.
|
|
220
|
+
|
|
221
|
+
A mismatch is not a reason to sign anyone out. They have a valid Connect
|
|
222
|
+
account — just the wrong kind of one for this application. `<ConnectAccessGate>`
|
|
223
|
+
already blocks the shell on it.
|
|
224
|
+
|
|
225
|
+
In the browser, the same two facts are `useConnectWorkspaceType()` and
|
|
226
|
+
`useConnectAudience()`, and `<ConnectWorkspaceTypeGate>` is the declarative form:
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
<ConnectWorkspaceTypeGate type="business">
|
|
230
|
+
<TeamMembersTable />
|
|
231
|
+
</ConnectWorkspaceTypeGate>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
That gate varies what each account type sees *inside* an application both
|
|
235
|
+
audiences can reach. It is not the audience gate and does not replace
|
|
236
|
+
`<ConnectAccessGate>`.
|
|
237
|
+
|
|
84
238
|
**4. On logout.**
|
|
85
239
|
|
|
86
240
|
```ts
|
|
@@ -135,14 +289,14 @@ default: worth it for an app shell, less so for a marketing page.
|
|
|
135
289
|
```ts
|
|
136
290
|
import { blockingBlockers } from "@cosmoops/connect-sdk"; // isomorphic
|
|
137
291
|
|
|
138
|
-
const readiness = await connect.entitlements.getReadiness(
|
|
292
|
+
const readiness = await connect.entitlements.getReadiness(workspaceId);
|
|
139
293
|
|
|
140
294
|
if (!readiness.ready) {
|
|
141
295
|
const [blocker] = blockingBlockers(readiness);
|
|
142
296
|
return showBlocked(blocker.message, blocker.resolveUrl); // resolveUrl links into Connect
|
|
143
297
|
}
|
|
144
298
|
|
|
145
|
-
if (await connect.entitlements.hasFeature(
|
|
299
|
+
if (await connect.entitlements.hasFeature(workspaceId, "campaign-module")) {
|
|
146
300
|
// ...
|
|
147
301
|
}
|
|
148
302
|
```
|
|
@@ -153,7 +307,7 @@ Branch on `blocker.code`, never on `message`. Treat an unrecognised code as bloc
|
|
|
153
307
|
|
|
154
308
|
```ts
|
|
155
309
|
const { balance } = await connect.billing.consumeAddon({
|
|
156
|
-
|
|
310
|
+
workspaceId,
|
|
157
311
|
addonId: "api-calls",
|
|
158
312
|
quantity: 1,
|
|
159
313
|
});
|
|
@@ -204,7 +358,7 @@ await client.logs.write({
|
|
|
204
358
|
});
|
|
205
359
|
```
|
|
206
360
|
|
|
207
|
-
Both scopes on the stored entry come from something verified, never from your payload: `
|
|
361
|
+
Both scopes on the stored entry come from something verified, never from your payload: `applicationId` from your API key, and `userId` from this token — which Connect checks is unrevoked, unexpired, and **minted for your application** before attributing anything. **There is no `userId` field to send.** An actor a caller can type is not an actor worth recording, and a conformance test fails the build if one ever appears in the server schema.
|
|
208
362
|
|
|
209
363
|
`write` returns `{ delivered, attributed }` and never throws. `attributed: false` with `delivered: true` means the token had expired (~15 days) or been revoked and the line was stored *without* a user — deliberately kept rather than rejected, because losing telemetry over a failed attribution is the worse of the two outcomes.
|
|
210
364
|
|
|
@@ -221,73 +375,20 @@ new ConnectClient({
|
|
|
221
375
|
});
|
|
222
376
|
```
|
|
223
377
|
|
|
224
|
-
## Next.js —
|
|
225
|
-
|
|
226
|
-
Two files, no arguments.
|
|
227
|
-
|
|
228
|
-
```ts
|
|
229
|
-
// app/api/connect/[...connect]/route.ts
|
|
230
|
-
export { GET, POST } from "@cosmoops/connect-sdk/next";
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
```tsx
|
|
234
|
-
// app/layout.tsx
|
|
235
|
-
import { ConnectProvider } from "@cosmoops/connect-sdk/client";
|
|
236
|
-
|
|
237
|
-
<ConnectProvider>{children}</ConnectProvider>
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
```css
|
|
241
|
-
/* app/globals.css */
|
|
242
|
-
@import "tailwindcss";
|
|
243
|
-
@import "@heroui/styles";
|
|
244
|
-
@import "@cosmoops/connect-sdk/theme.css";
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
`npx @cosmoops/connect-sdk init` writes the route file and a `/plans` page for
|
|
248
|
-
you; the provider and the stylesheet are the two it won't edit on your behalf.
|
|
249
|
-
|
|
250
|
-
That gives you the whole round trip — `/signin`, `/callback`, an httpOnly session
|
|
251
|
-
cookie, `/session`, `/entitlements`, `/plans`, `/addons`, `/apps`, `/sessions`,
|
|
252
|
-
`/logout`. Config comes from the environment:
|
|
253
|
-
|
|
254
|
-
| Variable | Required | Purpose |
|
|
255
|
-
| --- | --- | --- |
|
|
256
|
-
| `CONNECT_API_KEY` | yes | server-side credential |
|
|
257
|
-
| `CONNECT_CLIENT_ID` | yes | OAuth client id |
|
|
258
|
-
| `CONNECT_APP_URL` | no | non-production Connect instance |
|
|
259
|
-
| `CONNECT_SESSION_COOKIE` | no | session cookie name |
|
|
260
|
-
|
|
261
|
-
Register `{your origin}/api/connect/callback` as a redirect URI in the service's
|
|
262
|
-
Integration tab — the one step this cannot do for you.
|
|
378
|
+
## Next.js — reading the session
|
|
263
379
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
380
|
+
The setup is [Quickstart](#quickstart-nextjs) above; this is the detail behind
|
|
381
|
+
its step 6 — `getConnectSession()` / `requireConnectSession()` beyond the
|
|
382
|
+
one-liner shown there.
|
|
267
383
|
|
|
268
|
-
|
|
384
|
+
`requireConnectSession()` is `getConnectSession()` plus the check-and-redirect.
|
|
385
|
+
Reach for the plain version when a missing session isn't an error — an
|
|
386
|
+
optional account badge in a header, say:
|
|
269
387
|
|
|
270
388
|
```tsx
|
|
271
|
-
// app/dashboard/page.tsx
|
|
272
389
|
import { getConnectSession } from "@cosmoops/connect-sdk/next";
|
|
273
|
-
import { redirect } from "next/navigation";
|
|
274
|
-
|
|
275
|
-
export default async function DashboardPage() {
|
|
276
|
-
const session = await getConnectSession(); // UserResponse | null
|
|
277
|
-
if (!session) redirect("/api/connect/signin?next=/dashboard");
|
|
278
|
-
return <h1>{session.user.organizationName}</h1>;
|
|
279
|
-
}
|
|
280
|
-
```
|
|
281
390
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
```tsx
|
|
285
|
-
import { requireConnectSession } from "@cosmoops/connect-sdk/next";
|
|
286
|
-
|
|
287
|
-
export default async function DashboardPage() {
|
|
288
|
-
const session = await requireConnectSession(); // UserResponse — redirects if signed out
|
|
289
|
-
return <h1>{session.user.organizationName}</h1>;
|
|
290
|
-
}
|
|
391
|
+
const session = await getConnectSession(); // UserResponse | null
|
|
291
392
|
```
|
|
292
393
|
|
|
293
394
|
Pass a `Request` — `getConnectSession(request)` — anywhere `next/headers` does
|
|
@@ -303,9 +404,6 @@ It reads `cookies()` transitively, so under `cacheComponents` it belongs inside
|
|
|
303
404
|
a `<Suspense>` boundary like any other request-scoped read. A page that awaits
|
|
304
405
|
it above every boundary gives up its static shell.
|
|
305
406
|
|
|
306
|
-
You do **not** need `transpilePackages`. `dist/` is ES2022 with `"use client"`
|
|
307
|
-
already hoisted, so Turbopack and webpack consume it as-is.
|
|
308
|
-
|
|
309
407
|
## Route protection and `proxy.ts`
|
|
310
408
|
|
|
311
409
|
Nothing here is auto-wired, and nothing needs to be: this SDK ships no proxy and
|
|
@@ -340,11 +438,24 @@ export const config = { matcher: ["/dashboard/:path*", "/settings/:path*"] };
|
|
|
340
438
|
|
|
341
439
|
**A cookie is not a session, and this check is not authorization.** The cookie
|
|
342
440
|
holds an opaque Connect `sessionToken`; deciding whether it is still valid means
|
|
343
|
-
asking Connect, and proxy runs on every matched request —
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
441
|
+
asking Connect, and proxy runs on every matched request — including
|
|
442
|
+
[prefetched](https://nextjs.org/docs/app/getting-started/linking-and-navigating#prefetching)
|
|
443
|
+
ones — so a real check here would put a network round trip in front of far
|
|
444
|
+
more requests than the visitor actually makes.
|
|
445
|
+
|
|
446
|
+
This isn't just this SDK's opinion — it's what Next.js's own [Authentication
|
|
447
|
+
guide](https://nextjs.org/docs/app/guides/authentication#optimistic-checks-with-proxy-optional)
|
|
448
|
+
recommends, word for word (verified against the docs bundled with the Next
|
|
449
|
+
version in this repo's `node_modules`, since the hosted docs move faster than
|
|
450
|
+
any one install):
|
|
451
|
+
|
|
452
|
+
> Since Proxy runs on every route, including prefetched routes, it's important
|
|
453
|
+
> to only read the session from the cookie (optimistic checks), and avoid
|
|
454
|
+
> database checks to prevent performance issues.
|
|
455
|
+
|
|
456
|
+
The snippet above is exactly that: a presence check, nothing more. Next is
|
|
457
|
+
equally direct that this is as far as it goes — optimistic checks are "not
|
|
458
|
+
\[...\] a full session management or authorization solution."
|
|
348
459
|
|
|
349
460
|
An expired or revoked token still arrives as a cookie and still passes the
|
|
350
461
|
snippet above. The authoritative check is `getConnectSession()` in the Server
|
|
@@ -405,19 +516,19 @@ that URL yourself instead.
|
|
|
405
516
|
|
|
406
517
|
```tsx
|
|
407
518
|
import {
|
|
408
|
-
|
|
519
|
+
AppShell,
|
|
409
520
|
ConnectLoginButton, ConnectRegisterButton,
|
|
410
|
-
|
|
521
|
+
ConnectUserButton, ConnectAppsGrid, ConnectPlans,
|
|
411
522
|
} from "@cosmoops/connect-sdk/client";
|
|
412
523
|
|
|
413
|
-
// The whole frame —
|
|
414
|
-
<
|
|
524
|
+
// The whole frame — the rail, the access gate, Ask AI. See "App shell" below.
|
|
525
|
+
<AppShell name="Dispatch" nav={nav} secondaryNav={settingsNav}>{children}</AppShell>
|
|
415
526
|
|
|
416
527
|
// Or the pieces, if you already have a layout. The pair swaps automatically
|
|
417
528
|
// on sign-in state.
|
|
418
|
-
<ConnectAppsGrid />
|
|
529
|
+
<ConnectAppsGrid variant="icon" />
|
|
419
530
|
<ConnectLoginButton />
|
|
420
|
-
<
|
|
531
|
+
<ConnectUserButton variant="inline" />
|
|
421
532
|
|
|
422
533
|
// Pricing page
|
|
423
534
|
<ConnectPlans />
|
|
@@ -425,19 +536,25 @@ import {
|
|
|
425
536
|
|
|
426
537
|
| Component | What it does |
|
|
427
538
|
| --- | --- |
|
|
428
|
-
| `
|
|
429
|
-
| `
|
|
539
|
+
| `AppShell` | **The whole application frame** — one collapsible rail carrying the brand, workspace switcher, both navs, the launcher and the account row, with your pages beside it. No top bar. Mounts `ConnectAccessGate` for you. The one `/client` export without the `Connect` prefix; alias it on import if your own layout shares the name. |
|
|
540
|
+
| `ConnectSidebar` · `ConnectSidebarHeader` · `ConnectSidebarBody` · `ConnectSidebarFooter` · `ConnectSidebarRow` | The rail in pieces, for chrome the props cannot express. `AppShell` composes exactly these; they coordinate through `ConnectShellProvider`. |
|
|
541
|
+
| `ConnectSidebarNav` | The rail's nav column on its own — sections, one level of children, active-route highlight — for an app that already has its own chrome. (Was `ConnectSideNav`.) |
|
|
430
542
|
| `ConnectLoginButton` · `ConnectRegisterButton` | "Continue with Connect", in the shape people know from Google. Render nothing when signed in. |
|
|
431
|
-
| `
|
|
543
|
+
| `ConnectUserButton` | The account row at the foot of the rail — circular avatar, name over email — opening a modal with identity, sessions and the Connect destinations. Profile, Manage subscription and Open Connect each open Connect in a new tab; Sign out acts in place. Renders nothing when signed out. (Was `ConnectUserMenu`.) |
|
|
544
|
+
| `ConnectProfileModal` | The same modal with no trigger, driven by `isOpen`/`onOpenChange` — for an app keeping its own account control. |
|
|
545
|
+
| `ConnectWorkspaceSwitcher` | Sits in the rail's header. Hides itself for an account with one workspace; shows unusable ones disabled, with the reason. |
|
|
432
546
|
| `ConnectAppsGrid` | Google-style launcher — grid icon opening a modal of every app this workspace can open. |
|
|
433
547
|
| `ConnectPlans` | Monthly/annual toggle, features, add-ons, live discount, coupon field, and the subscribe / upgrade / downgrade / cancel flow. |
|
|
434
548
|
| `ConnectPlansPage` | A whole `/plans` page — readiness banner, current subscription, and the grid. What `connect-sdk init` scaffolds. |
|
|
435
549
|
| `ConnectSubscribeDialog` | The review step `ConnectPlans` opens — plan, GST, what happens to the current cycle. Exported so you can drive the flow from your own pricing page. |
|
|
550
|
+
| `ConnectAskAi` | **Ask AI** — a chat panel grounded in the workspace's Context Index, streamed through your own bridge. Answers draw as well as write: tables, charts, metric tiles. Mounted by `AppShell` by default. |
|
|
551
|
+
| `AiTable` · `AiChart` · `AiMetrics` · `AiTimeline` · `AiDetails` · `AiSteps` · `AiSources` | The seven widgets an Ask AI answer can draw, exported for rendering Connect's chat somewhere other than the panel. |
|
|
436
552
|
| `ConnectAccessGate` | **The one gate to mount if you mount one.** Composes suspended / no-plan / onboarding-pending / payment-due into a single priority-ordered decision. Wrap your app content once. |
|
|
437
553
|
| `ConnectSubscriptionGate` | Renders your module for a subscribed workspace, and `ConnectNoSubscriptionCard` for everyone else. Never flashes the upsell while loading. |
|
|
438
554
|
| `ConnectNoSubscriptionCard` · `ConnectSubscriptionCard` | The same slot, unsubscribed and subscribed. Plan, status, renewal date, seats, and one way into Connect. |
|
|
439
|
-
| `ConnectReadinessBanner` · `ConnectReadinessGate` | Every gate between this workspace and your
|
|
555
|
+
| `ConnectReadinessBanner` · `ConnectReadinessGate` | Every gate between this workspace and your application — KYC, payment method, seat headroom — each with the Connect screen that fixes it. |
|
|
440
556
|
| `ConnectFeatureGate` | Renders a module only when `plan.features` includes its key. |
|
|
557
|
+
| `ConnectWorkspaceTypeGate` | Renders a subtree only for an `"individual"` or `"business"` workspace — for dead controls, not access. |
|
|
441
558
|
| `ConnectTrialBanner` | A running trial and the date it converts. Renders nothing otherwise. |
|
|
442
559
|
| `ConnectAccountSessions` | Every device signed into this Connect account, with revoke. |
|
|
443
560
|
| `ConnectUsageMeter` | Remaining balance of each consumable add-on, with overage and carry-forward. |
|
|
@@ -446,12 +563,12 @@ import {
|
|
|
446
563
|
|
|
447
564
|
### Hooks
|
|
448
565
|
|
|
449
|
-
`useConnect` · `useConnectUser` · `useActivePath` · `useConnectAccessGate` · `useConnectEntitlements` · `useConnectPlans` · `useConnectApps` · `useConnectAddonUsage` · `useConnectSessions` · `useConnectResource` · `useConnectFeature` · `useConnectCheckout`
|
|
566
|
+
`useConnect` · `useConnectUser` · `useConnectWorkspaceType` · `useConnectAudience` · `useActivePath` · `useConnectAccessGate` · `useConnectEntitlements` · `useConnectPlans` · `useConnectApps` · `useConnectAddonUsage` · `useConnectSessions` · `useConnectResource` · `useConnectFeature` · `useConnectCheckout`
|
|
450
567
|
|
|
451
568
|
### Metering
|
|
452
569
|
|
|
453
570
|
```tsx
|
|
454
|
-
await client.billing.consumeAddon({
|
|
571
|
+
await client.billing.consumeAddon({ workspaceId, addonId: "addon_sms", quantity: 1 }); // server
|
|
455
572
|
<ConnectUsageMeter /> // browser
|
|
456
573
|
```
|
|
457
574
|
|
|
@@ -461,7 +578,7 @@ Invoices stay a deep link (`ConnectInvoicesButton`). A GST tax invoice is a lega
|
|
|
461
578
|
|
|
462
579
|
### Plan features: label vs key
|
|
463
580
|
|
|
464
|
-
The catalogue read (`useConnectPlans`, `<ConnectPlans />`) reports `features` as `{ key, name, description }` — **render `name`, gate on `key`**. The subscription read (`useConnectEntitlements`, `hasFeature`, `<ConnectFeatureGate>`) reports `features` as a bare `string[]` of keys, and stays that way: that one is a contract your backend compares against, and a display label is neither stable nor comparable. `name` falls back to the key for a plan older than its
|
|
581
|
+
The catalogue read (`useConnectPlans`, `<ConnectPlans />`) reports `features` as `{ key, name, description }` — **render `name`, gate on `key`**. The subscription read (`useConnectEntitlements`, `hasFeature`, `<ConnectFeatureGate>`) reports `features` as a bare `string[]` of keys, and stays that way: that one is a contract your backend compares against, and a display label is neither stable nor comparable. `name` falls back to the key for a plan older than its application's feature catalog.
|
|
465
582
|
|
|
466
583
|
### Gates
|
|
467
584
|
|
|
@@ -481,7 +598,7 @@ the order yourself:
|
|
|
481
598
|
| State | What renders |
|
|
482
599
|
| --- | --- |
|
|
483
600
|
| `suspended` | A hard stop. Nothing the customer does clears it — only a Connect admin reactivating the workspace. |
|
|
484
|
-
| `no_subscription` | "Choose a plan", linking to Connect's subscribe page for your
|
|
601
|
+
| `no_subscription` | "Choose a plan", linking to Connect's subscribe page for your application. |
|
|
485
602
|
| `onboarding_pending` | Waiting on a reviewer. No action offered, because there is none. |
|
|
486
603
|
| `blocked` | Any other blocking readiness code — verification, audience, seats — each with its own fix. |
|
|
487
604
|
| `payment_retry` | **Your app still renders.** A dismissible banner counts down to the dunning deadline. |
|
|
@@ -519,7 +636,7 @@ The narrower gates still exist for gating *within* a cleared workspace:
|
|
|
519
636
|
|
|
520
637
|
All of them render `loading` — never the fallback — while the answer is unknown, so a paying customer is never flashed an upsell for something they already have.
|
|
521
638
|
|
|
522
|
-
**They are UX gates, not security boundaries.** Everything they read reaches your own origin and a browser can ignore it. The authoritative check is on your server: `client.entitlements.getReadiness(
|
|
639
|
+
**They are UX gates, not security boundaries.** Everything they read reaches your own origin and a browser can ignore it. The authoritative check is on your server: `client.entitlements.getReadiness(workspaceId)` and `client.entitlements.hasFeature(workspaceId, feature)`.
|
|
523
640
|
|
|
524
641
|
### Scaffolding
|
|
525
642
|
|
|
@@ -579,12 +696,12 @@ Cancelling uses the same machinery via `buildCancelUrl`. There is no *resume*: C
|
|
|
579
696
|
"use client";
|
|
580
697
|
import Link from "next/link";
|
|
581
698
|
import { usePathname } from "next/navigation";
|
|
582
|
-
import {
|
|
699
|
+
import { AppShell } from "@cosmoops/connect-sdk/client";
|
|
583
700
|
import { LayoutDashboard, Receipt, Settings, Truck } from "lucide-react";
|
|
584
701
|
|
|
585
702
|
export function Shell({ children }: { children: React.ReactNode }) {
|
|
586
703
|
return (
|
|
587
|
-
<
|
|
704
|
+
<AppShell
|
|
588
705
|
name="Dispatch"
|
|
589
706
|
icon={<Truck />}
|
|
590
707
|
activePath={usePathname()}
|
|
@@ -592,33 +709,38 @@ export function Shell({ children }: { children: React.ReactNode }) {
|
|
|
592
709
|
nav={[
|
|
593
710
|
{ label: "Overview", href: "/", icon: <LayoutDashboard className="size-5" /> },
|
|
594
711
|
{ label: "Shipments", href: "/shipments", icon: <Truck className="size-5" /> },
|
|
712
|
+
]}
|
|
713
|
+
// Pinned to the foot of the rail: the routes you go to in order to
|
|
714
|
+
// change how the job works, not to do it.
|
|
715
|
+
secondaryNav={[
|
|
595
716
|
{
|
|
596
|
-
|
|
717
|
+
label: "Settings",
|
|
718
|
+
href: "/settings",
|
|
719
|
+
icon: <Settings className="size-5" />,
|
|
597
720
|
items: [
|
|
598
|
-
{
|
|
599
|
-
|
|
600
|
-
href: "/settings",
|
|
601
|
-
icon: <Settings className="size-5" />,
|
|
602
|
-
items: [
|
|
603
|
-
{ label: "Members", href: "/settings/members" },
|
|
604
|
-
{ label: "Billing", href: "/settings/billing" },
|
|
605
|
-
],
|
|
606
|
-
},
|
|
607
|
-
{ label: "Plans", href: "/plans", icon: <Receipt className="size-5" />, gateExempt: true },
|
|
721
|
+
{ label: "Members", href: "/settings/members" },
|
|
722
|
+
{ label: "Billing", href: "/settings/billing" },
|
|
608
723
|
],
|
|
609
724
|
},
|
|
725
|
+
{ label: "Plans", href: "/plans", icon: <Receipt className="size-5" />, gateExempt: true },
|
|
610
726
|
]}
|
|
611
727
|
>
|
|
612
728
|
{children}
|
|
613
|
-
</
|
|
729
|
+
</AppShell>
|
|
614
730
|
);
|
|
615
731
|
}
|
|
616
732
|
```
|
|
617
733
|
|
|
618
|
-
That is the entire layout of
|
|
619
|
-
identical across the family — the lockup, the rail geometry, the
|
|
620
|
-
treatment, the app launcher, the account
|
|
621
|
-
here; only `name`, `icon` and
|
|
734
|
+
That is the entire layout of an application platform. Everything that should
|
|
735
|
+
look identical across the family — the lockup, the rail geometry, the
|
|
736
|
+
active-route treatment, the app launcher, the account row, the mobile drawer —
|
|
737
|
+
comes from here; only `name`, `icon` and the two navs differ per platform.
|
|
738
|
+
|
|
739
|
+
**There is no top bar.** The launcher and the account row live at the foot of
|
|
740
|
+
the rail, one press from wherever the pointer already is, and a page's own
|
|
741
|
+
heading belongs to the page — put it in `ConnectPageShell` from
|
|
742
|
+
`@cosmoops/connect-sdk/ui`. Below `md` a slim bar carries the drawer trigger
|
|
743
|
+
and nothing else.
|
|
622
744
|
|
|
623
745
|
**Routing is a prop.** The SDK ships into any React app and cannot import
|
|
624
746
|
`next/link`, so `renderLink` is how your router gets involved; the default is a
|
|
@@ -627,13 +749,13 @@ plain `<a>`, which works everywhere and costs a full page load. Pass
|
|
|
627
749
|
`window.location` and patches `history.pushState` to hear about client-side
|
|
628
750
|
navigations, which is guesswork your router never has to do.
|
|
629
751
|
|
|
630
|
-
**The access gate is on by default.** `
|
|
752
|
+
**The access gate is on by default.** `AppShell` is exactly the "wrap
|
|
631
753
|
your app content once" place `ConnectAccessGate` asks for, so it mounts one
|
|
632
754
|
around `children` — an app that ships a shell and forgets the gate is an app
|
|
633
755
|
that lets a suspended workspace keep working. The chrome is never gated: the
|
|
634
756
|
sidebar, the lockup, the account menu and sign-out stay reachable in every
|
|
635
757
|
blocked state. Routes that resolve a block rather than needing one — your
|
|
636
|
-
pricing page — are flagged where they are declared:
|
|
758
|
+
pricing page — are flagged where they are declared, in either nav:
|
|
637
759
|
|
|
638
760
|
```tsx
|
|
639
761
|
{ label: "Plans", href: "/plans", gateExempt: true }
|
|
@@ -644,28 +766,51 @@ page that sells them one, and is told to choose a plan. Pass `gate={false}` to
|
|
|
644
766
|
opt out entirely, or `gateExemptRoutes` for a path that is not in the sidebar
|
|
645
767
|
at all.
|
|
646
768
|
|
|
769
|
+
**Ask AI is on by default too**, for the same reason: a family of application
|
|
770
|
+
platforms should answer questions the same way, and a feature every partner has
|
|
771
|
+
to remember to switch on is a feature most of them do not have. It stays inert
|
|
772
|
+
until it can work — nothing renders for a signed-out visitor, and a workspace
|
|
773
|
+
whose Context Index is off gets a message in the panel rather than a broken
|
|
774
|
+
answer. Pass `askAi={false}` to leave it out, or an object to override its
|
|
775
|
+
heading, copy or markdown plugins.
|
|
776
|
+
|
|
777
|
+
It needs two things you control: the workspace's Context Index turned on in
|
|
778
|
+
Connect, and `ai:chat` on the API key behind your bridge.
|
|
779
|
+
|
|
647
780
|
Other things worth knowing:
|
|
648
781
|
|
|
649
|
-
- **The rail
|
|
650
|
-
(`localStorage`).
|
|
651
|
-
|
|
652
|
-
|
|
782
|
+
- **The rail starts collapsed** and remembers the choice per browser
|
|
783
|
+
(`localStorage`). The icon rail is the resting state; expanding is the
|
|
784
|
+
deliberate act. Expanded, the collapse control sits at the header's trailing
|
|
785
|
+
edge; collapsed, hovering the lockup swaps it for the expand glyph — so the
|
|
786
|
+
rail spends no row on a permanent control, at the cost of the lockup no
|
|
787
|
+
longer being a link home while collapsed. Pass `defaultCollapsed={false}` to
|
|
788
|
+
open by default, drive it with `collapsed` + `onCollapsedChange`, or pin it
|
|
789
|
+
open with `collapsible={false}`.
|
|
790
|
+
- **The collapse animates width and nothing else.** Every row is laid out so
|
|
791
|
+
its glyph sits on the same horizontal centre in both states, and labels are
|
|
792
|
+
clipped rather than re-measured — so no row reflows and nothing drifts
|
|
793
|
+
sideways mid-transition.
|
|
794
|
+
- **Below `md` there is no rail**, only a drawer behind the mobile bar's menu
|
|
653
795
|
button — a 64px icon strip on a phone is a worse drawer, not a smaller one.
|
|
654
796
|
- **Children unfold only on the branch you are on.** A sidebar that opens
|
|
655
797
|
every branch at once is a sitemap.
|
|
656
798
|
- **No `isSignedIn` branch, in here or in yours.** The auth buttons render
|
|
657
|
-
nothing once a session exists, the account
|
|
799
|
+
nothing once a session exists, the account row renders nothing without one,
|
|
658
800
|
and the launcher renders nothing when signed out.
|
|
659
|
-
- `
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
- `theme` / `onThemeChange` pass straight through to `
|
|
801
|
+
- `secondaryNav`, `sidebarFooter` and `footer` are slots; `contentClassName`
|
|
802
|
+
replaces the content pane's default max-width and padding. (`title` and
|
|
803
|
+
`headerActions` are gone with the top bar — see the 0.12.0 changelog entry.)
|
|
804
|
+
- `theme` / `onThemeChange` pass straight through to `ConnectUserButton` — the
|
|
663
805
|
SDK has no theme provider of its own, deliberately.
|
|
664
806
|
|
|
665
|
-
`
|
|
666
|
-
likes
|
|
667
|
-
|
|
668
|
-
|
|
807
|
+
`ConnectSidebarNav` is the nav column alone, for an app with chrome it already
|
|
808
|
+
likes, and `ConnectSidebar` / `ConnectSidebarHeader` / `ConnectSidebarBody` /
|
|
809
|
+
`ConnectSidebarFooter` / `ConnectSidebarRow` are the rest of the rail — the
|
|
810
|
+
exact pieces `AppShell` composes, so chrome the props cannot express is a
|
|
811
|
+
rearrangement rather than a fork. `resolveActivePath` and `isNavItemActive` are
|
|
812
|
+
the same pure functions the rail highlights with, exported so a breadcrumb or a
|
|
813
|
+
document title derived from your nav cannot disagree with it.
|
|
669
814
|
|
|
670
815
|
### Logo
|
|
671
816
|
|
|
@@ -686,7 +831,7 @@ Each platform passes only what differs — `name` and `icon`. Mark geometry, wor
|
|
|
686
831
|
@import "@cosmoops/connect-sdk/theme.css";
|
|
687
832
|
```
|
|
688
833
|
|
|
689
|
-
One shared **HeroUI v3** theme for every
|
|
834
|
+
One shared **HeroUI v3** theme for every application platform. The components paint with semantic tokens only — `accent`, `muted`, `surface`, `separator`, `danger` — never a hardcoded colour, so one edit to the theme restyles all of them everywhere at once. To diverge on brand, redefine individual tokens *after* the import rather than forking the file.
|
|
690
835
|
|
|
691
836
|
Requires `@heroui/react`, `lucide-react` and `motion` as peers (optional — server-only consumers need none of them). `motion` drives the components' entrance and state transitions, and respects `prefers-reduced-motion`.
|
|
692
837
|
|
|
@@ -696,6 +841,113 @@ The *flow* is in your app; the *payment* is not. A plan change is not one write
|
|
|
696
841
|
|
|
697
842
|
So the hand-off is a URL, and Connect renders it as a modal over the relevant page (an intercepted parallel route) — the customer lands on the confirmation for the plan they picked, and returns to a subscription that is already correct.
|
|
698
843
|
|
|
844
|
+
## Release notes
|
|
845
|
+
|
|
846
|
+
Full history: [CHANGELOG.md](./CHANGELOG.md). Latest first:
|
|
847
|
+
|
|
848
|
+
### 0.12.0 — breaking
|
|
849
|
+
|
|
850
|
+
The application shell is one column. **The top bar is gone**; the rail holds
|
|
851
|
+
everything it used to, and starts collapsed.
|
|
852
|
+
|
|
853
|
+
- **`title` and `headerActions` are removed** with the bar that held them. A
|
|
854
|
+
page's heading goes in `ConnectPageShell` (from `/ui`), next to that page's
|
|
855
|
+
own breadcrumbs and actions; an application-level control goes in
|
|
856
|
+
`sidebarFooter` as a `ConnectSidebarRow`.
|
|
857
|
+
- **`defaultCollapsed` now defaults to `true`.** Pass `false` for 0.11's
|
|
858
|
+
behaviour.
|
|
859
|
+
- **`ConnectSideNav` → `ConnectSidebarNav`**, **`ConnectUserMenu` →
|
|
860
|
+
`ConnectUserButton`**. Both old names remain as deprecated aliases.
|
|
861
|
+
`AppShell` keeps its name — the one export here without the `Connect`
|
|
862
|
+
prefix, as decided in 0.8.0.
|
|
863
|
+
- **New:** `secondaryNav` (a second nav group pinned to the foot of the rail),
|
|
864
|
+
`ConnectProfileModal` (the account modal with no trigger), and the rail's
|
|
865
|
+
own pieces — `ConnectSidebar`, `ConnectSidebarHeader`, `ConnectSidebarBody`,
|
|
866
|
+
`ConnectSidebarFooter`, `ConnectSidebarRow`, `ConnectShellProvider`,
|
|
867
|
+
`useConnectShell`, `sidebarRowClass`.
|
|
868
|
+
- **The workspace switcher moved into the rail's header** and is collapse-aware;
|
|
869
|
+
the account row sits at the foot with a circular avatar.
|
|
870
|
+
- **Fixed:** the closed Ask AI panel occupied 400px at `lg` and up on every page
|
|
871
|
+
with `askAi` on; sidebar contrast (the active row was **below WCAG AA** in
|
|
872
|
+
both schemes); the launcher and account rows looked permanently selected.
|
|
873
|
+
|
|
874
|
+
### 0.11.0 — breaking
|
|
875
|
+
|
|
876
|
+
- **`ConnectUser.workspaceId`, `workspaceName` and `workspaceType` are
|
|
877
|
+
nullable.** They are `null` for exactly one thing: a **Connect platform
|
|
878
|
+
staff** session, which belongs to no workspace. If you never enable staff
|
|
879
|
+
access — it is off by default — this is a type narrow and nothing else.
|
|
880
|
+
`?? ""` is the wrong fix: decide what your tenanting does with a session that
|
|
881
|
+
has no tenant.
|
|
882
|
+
- **Connect platform staff sign-in**, `platformAccess` on `exchangeCode()` and
|
|
883
|
+
`getUser()`, `<ConnectPlatformGate>` and `useConnectPlatformAccess()`.
|
|
884
|
+
|
|
885
|
+
### 0.10.0
|
|
886
|
+
|
|
887
|
+
- **`@cosmoops/connect-sdk/ui`** — `ConnectPageShell`, `ConnectSection`,
|
|
888
|
+
`ConnectEmptyState`, `ConnectStatusChip`, `ConnectSettingRow`. Deliberately
|
|
889
|
+
*not* a `"use client"` module, so a page shell does not become a client
|
|
890
|
+
boundary.
|
|
891
|
+
- `ConnectLinkButton`, `ConnectConfirmDialog`, `ConnectCursorPagination`,
|
|
892
|
+
`ConnectFilterTabs`, `ConnectAccountSessions` — built since 0.8 and reachable
|
|
893
|
+
from no entry point until now.
|
|
894
|
+
- `ConnectWorkspaceSwitcher`, `useConnectWorkspaces()`,
|
|
895
|
+
`useConnectWorkspaceMembers()`, and the `workspaces` resource on the server
|
|
896
|
+
client.
|
|
897
|
+
|
|
898
|
+
### 0.9.0
|
|
899
|
+
|
|
900
|
+
- **Ask AI**, mounted by `AppShell` by default — a chat panel grounded in the
|
|
901
|
+
workspace's Context Index, streamed through a new `POST /ai/chat` route on
|
|
902
|
+
your own bridge. The workspace comes from the caller's session, never the
|
|
903
|
+
browser.
|
|
904
|
+
- **Answers draw as well as write.** Connect's model calls presentation tools
|
|
905
|
+
and the panel renders them natively: a sortable table with CSV export, a
|
|
906
|
+
chart, metric tiles, timelines, fact sheets, procedures, citations. Values
|
|
907
|
+
arrive raw with a format tag, so they are formatted in the viewer's locale
|
|
908
|
+
and a column still sorts numerically. The seven widgets are exported.
|
|
909
|
+
- **`streamdown` is a new optional peer dependency** — see step 1 and step 4 of
|
|
910
|
+
the quickstart. Two lines of host setup; skip both with `askAi={false}`.
|
|
911
|
+
- **`user.workspaceType`, `audience`**, `<ConnectWorkspaceTypeGate>`,
|
|
912
|
+
`useConnectWorkspaceType()`, `useConnectAudience()`.
|
|
913
|
+
- **`streamResource()`** on `useConnect()` — POST through the bridge and get
|
|
914
|
+
the raw streaming `Response` back.
|
|
915
|
+
- **Fixed:** `ai.chat()` no longer has its stream cut off mid-answer by the
|
|
916
|
+
transport's 20s timeout.
|
|
917
|
+
|
|
918
|
+
*0.8.0 was never published; its one breaking change is in this release.*
|
|
919
|
+
|
|
920
|
+
### 0.8.0 — breaking (folded into 0.9.0)
|
|
921
|
+
|
|
922
|
+
- **`ConnectAppShell` renamed to `AppShell`.** Same props, same behaviour —
|
|
923
|
+
only the name changed. Every other `/client` export keeps its `Connect`
|
|
924
|
+
prefix; this is the one deliberate exception.
|
|
925
|
+
|
|
926
|
+
```diff
|
|
927
|
+
- import { ConnectAppShell, type ConnectAppShellProps } from "@cosmoops/connect-sdk/client";
|
|
928
|
+
+ import { AppShell, type AppShellProps } from "@cosmoops/connect-sdk/client";
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
If your own layout component is also named `AppShell`, alias the import:
|
|
932
|
+
`import { AppShell as ConnectAppShell } from "..."`.
|
|
933
|
+
|
|
934
|
+
### 0.6.0
|
|
935
|
+
|
|
936
|
+
- **`AppShell`** (then `ConnectAppShell`) — the application frame every
|
|
937
|
+
platform was otherwise writing by hand: collapsible sidebar, top bar with
|
|
938
|
+
the app launcher and account menu, mobile drawer, `ConnectAccessGate`
|
|
939
|
+
mounted around your content by default.
|
|
940
|
+
- **`ConnectSideNav`** — the sidebar's nav column alone, for an app with
|
|
941
|
+
chrome it already likes.
|
|
942
|
+
- The nav model as data — `ConnectNav` / `ConnectNavItem` / `ConnectNavSection`
|
|
943
|
+
— plus the pure functions the rail resolves the active route with.
|
|
944
|
+
|
|
945
|
+
See [CHANGELOG.md](./CHANGELOG.md) for 0.5.0 and earlier.
|
|
946
|
+
|
|
947
|
+
Upgrading past a minor version? Read that version's entry first — per the 0.x
|
|
948
|
+
convention at the top of this file, a minor bump here is allowed to break
|
|
949
|
+
something, and the changelog says what and how to migrate.
|
|
950
|
+
|
|
699
951
|
## Development
|
|
700
952
|
|
|
701
953
|
```bash
|