@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.
Files changed (154) hide show
  1. package/CHANGELOG.md +662 -0
  2. package/README.md +376 -124
  3. package/dist/cjs/cli/doctor.js +3 -3
  4. package/dist/cjs/cli/init.js +3 -3
  5. package/dist/cjs/client/components/access-gate.js +59 -5
  6. package/dist/cjs/client/components/ai-format.js +135 -0
  7. package/dist/cjs/client/components/ai-widgets.js +232 -0
  8. package/dist/cjs/client/components/app-shell.js +80 -45
  9. package/dist/cjs/client/components/apps-grid.js +3 -2
  10. package/dist/cjs/client/components/ask-ai.js +353 -0
  11. package/dist/cjs/client/components/confirm-dialog.js +40 -0
  12. package/dist/cjs/client/components/cursor-pagination.js +22 -0
  13. package/dist/cjs/client/components/filter-tabs.js +24 -0
  14. package/dist/cjs/client/components/grievance-notice.js +30 -0
  15. package/dist/cjs/client/components/link-button.js +17 -10
  16. package/dist/cjs/client/components/logo.js +4 -3
  17. package/dist/cjs/client/components/nav.js +42 -1
  18. package/dist/cjs/client/components/plan-pricing.js +27 -0
  19. package/dist/cjs/client/components/plans.js +1 -1
  20. package/dist/cjs/client/components/platform-gate.js +48 -0
  21. package/dist/cjs/client/components/shell-context.js +149 -0
  22. package/dist/cjs/client/components/side-nav.js +50 -24
  23. package/dist/cjs/client/components/sidebar.js +127 -0
  24. package/dist/cjs/client/components/subscribe-dialog.js +2 -2
  25. package/dist/cjs/client/components/subscription.js +1 -1
  26. package/dist/cjs/client/components/user-menu.js +122 -40
  27. package/dist/cjs/client/components/workspace-switcher.js +100 -0
  28. package/dist/cjs/client/components/workspace-type-gate.js +48 -0
  29. package/dist/cjs/client/index.js +98 -3
  30. package/dist/cjs/client/provider.js +100 -2
  31. package/dist/cjs/client/use-checkout.js +9 -5
  32. package/dist/cjs/errors.js +1 -1
  33. package/dist/cjs/index.js +11 -1
  34. package/dist/cjs/next/index.js +2 -2
  35. package/dist/cjs/server/client.js +25 -2
  36. package/dist/cjs/server/handler.js +237 -16
  37. package/dist/cjs/server/http.js +52 -26
  38. package/dist/cjs/server/resources/acting-as.js +16 -0
  39. package/dist/cjs/server/resources/ai.js +15 -2
  40. package/dist/cjs/server/resources/data.js +374 -0
  41. package/dist/cjs/server/resources/entitlements.js +41 -0
  42. package/dist/cjs/server/resources/identity.js +9 -3
  43. package/dist/cjs/server/resources/kv.js +84 -0
  44. package/dist/cjs/server/resources/platform.js +43 -0
  45. package/dist/cjs/server/resources/sessions.js +1 -1
  46. package/dist/cjs/server/resources/workspaces.js +74 -0
  47. package/dist/cjs/store.js +152 -0
  48. package/dist/cjs/ui/empty-state.js +28 -0
  49. package/dist/cjs/ui/index.js +44 -0
  50. package/dist/cjs/ui/page-header.js +27 -0
  51. package/dist/cjs/ui/page-shell.js +44 -0
  52. package/dist/cjs/ui/section.js +20 -0
  53. package/dist/cjs/ui/setting-row.js +14 -0
  54. package/dist/cjs/ui/status-chip.js +17 -0
  55. package/dist/esm/cli/doctor.js +3 -3
  56. package/dist/esm/cli/init.js +3 -3
  57. package/dist/esm/client/components/access-gate.js +60 -6
  58. package/dist/esm/client/components/ai-format.js +129 -0
  59. package/dist/esm/client/components/ai-widgets.js +223 -0
  60. package/dist/esm/client/components/app-shell.js +85 -50
  61. package/dist/esm/client/components/apps-grid.js +3 -2
  62. package/dist/esm/client/components/ask-ai.js +350 -0
  63. package/dist/esm/client/components/confirm-dialog.js +37 -0
  64. package/dist/esm/client/components/cursor-pagination.js +19 -0
  65. package/dist/esm/client/components/filter-tabs.js +21 -0
  66. package/dist/esm/client/components/grievance-notice.js +27 -0
  67. package/dist/esm/client/components/link-button.js +17 -10
  68. package/dist/esm/client/components/logo.js +4 -3
  69. package/dist/esm/client/components/nav.js +41 -1
  70. package/dist/esm/client/components/plan-pricing.js +25 -0
  71. package/dist/esm/client/components/plans.js +2 -2
  72. package/dist/esm/client/components/platform-gate.js +45 -0
  73. package/dist/esm/client/components/shell-context.js +141 -0
  74. package/dist/esm/client/components/side-nav.js +49 -22
  75. package/dist/esm/client/components/sidebar.js +120 -0
  76. package/dist/esm/client/components/subscribe-dialog.js +3 -3
  77. package/dist/esm/client/components/subscription.js +2 -2
  78. package/dist/esm/client/components/user-menu.js +121 -41
  79. package/dist/esm/client/components/workspace-switcher.js +97 -0
  80. package/dist/esm/client/components/workspace-type-gate.js +45 -0
  81. package/dist/esm/client/index.js +58 -5
  82. package/dist/esm/client/provider.js +94 -2
  83. package/dist/esm/client/use-checkout.js +9 -5
  84. package/dist/esm/errors.js +1 -1
  85. package/dist/esm/index.js +1 -0
  86. package/dist/esm/next/index.js +2 -2
  87. package/dist/esm/server/client.js +25 -2
  88. package/dist/esm/server/handler.js +237 -16
  89. package/dist/esm/server/http.js +52 -26
  90. package/dist/esm/server/resources/acting-as.js +13 -0
  91. package/dist/esm/server/resources/ai.js +15 -2
  92. package/dist/esm/server/resources/data.js +369 -0
  93. package/dist/esm/server/resources/entitlements.js +41 -0
  94. package/dist/esm/server/resources/identity.js +9 -3
  95. package/dist/esm/server/resources/kv.js +80 -0
  96. package/dist/esm/server/resources/platform.js +39 -0
  97. package/dist/esm/server/resources/sessions.js +1 -1
  98. package/dist/esm/server/resources/workspaces.js +70 -0
  99. package/dist/esm/store.js +141 -0
  100. package/dist/esm/ui/empty-state.js +25 -0
  101. package/dist/esm/ui/index.js +34 -0
  102. package/dist/esm/ui/page-header.js +24 -0
  103. package/dist/esm/ui/page-shell.js +41 -0
  104. package/dist/esm/ui/section.js +17 -0
  105. package/dist/esm/ui/setting-row.js +11 -0
  106. package/dist/esm/ui/status-chip.js +14 -0
  107. package/dist/theme.css +87 -0
  108. package/dist/types/client/components/access-gate.d.ts +17 -3
  109. package/dist/types/client/components/ai-format.d.ts +24 -0
  110. package/dist/types/client/components/ai-widgets.d.ts +42 -0
  111. package/dist/types/client/components/app-shell.d.ts +85 -31
  112. package/dist/types/client/components/apps-grid.d.ts +11 -2
  113. package/dist/types/client/components/ask-ai.d.ts +73 -0
  114. package/dist/types/client/components/confirm-dialog.d.ts +34 -0
  115. package/dist/types/client/components/cursor-pagination.d.ts +27 -0
  116. package/dist/types/client/components/filter-tabs.d.ts +26 -0
  117. package/dist/types/client/components/grievance-notice.d.ts +31 -0
  118. package/dist/types/client/components/link-button.d.ts +32 -9
  119. package/dist/types/client/components/logo.d.ts +18 -2
  120. package/dist/types/client/components/nav.d.ts +27 -3
  121. package/dist/types/client/components/plan-pricing.d.ts +10 -0
  122. package/dist/types/client/components/platform-gate.d.ts +53 -0
  123. package/dist/types/client/components/shell-context.d.ts +116 -0
  124. package/dist/types/client/components/side-nav.d.ts +28 -14
  125. package/dist/types/client/components/sidebar.d.ts +95 -0
  126. package/dist/types/client/components/user-menu.d.ts +70 -23
  127. package/dist/types/client/components/workspace-switcher.d.ts +49 -0
  128. package/dist/types/client/components/workspace-type-gate.d.ts +48 -0
  129. package/dist/types/client/index.d.ts +36 -7
  130. package/dist/types/client/provider.d.ts +149 -3
  131. package/dist/types/client/use-checkout.d.ts +8 -4
  132. package/dist/types/errors.d.ts +1 -1
  133. package/dist/types/index.d.ts +2 -0
  134. package/dist/types/next/index.d.ts +1 -1
  135. package/dist/types/server/client.d.ts +25 -2
  136. package/dist/types/server/http.d.ts +14 -3
  137. package/dist/types/server/index.d.ts +4 -0
  138. package/dist/types/server/resources/acting-as.d.ts +11 -0
  139. package/dist/types/server/resources/ai.d.ts +9 -1
  140. package/dist/types/server/resources/data.d.ts +258 -0
  141. package/dist/types/server/resources/entitlements.d.ts +33 -1
  142. package/dist/types/server/resources/kv.d.ts +63 -0
  143. package/dist/types/server/resources/platform.d.ts +32 -0
  144. package/dist/types/server/resources/workspaces.d.ts +55 -0
  145. package/dist/types/store.d.ts +96 -0
  146. package/dist/types/types.d.ts +491 -13
  147. package/dist/types/ui/empty-state.d.ts +34 -0
  148. package/dist/types/ui/index.d.ts +40 -0
  149. package/dist/types/ui/page-header.d.ts +34 -0
  150. package/dist/types/ui/page-shell.d.ts +39 -0
  151. package/dist/types/ui/section.d.ts +26 -0
  152. package/dist/types/ui/setting-row.d.ts +18 -0
  153. package/dist/types/ui/status-chip.d.ts +36 -0
  154. 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 service-partner API** (`/api/v1/service/*`).
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 service's Integration tab is also your OAuth2 `client_secret` and the key your `id_token` is signed with. Revoking it revokes all three.
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(orgId);
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(orgId, "campaign-module")) {
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
- organizationId: orgId,
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: `serviceId` from your API key, and `userId` from this token — which Connect checks is unrevoked, unexpired, and **minted for your service** 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.
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 — plug and play
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
- No `next.config.ts` changes needed, including `transpilePackages`: `dist/` is
265
- already ES2022 with `"use client"` hoisted, so Turbopack and webpack both
266
- consume it as-is.
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
- Reading the session server-side — no argument, no `headers()` dance:
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
- Or skip the check-and-redirect entirely — `requireConnectSession()` does both:
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 — so a real check here
344
- would put a network round trip in front of each one. Next's own guidance says
345
- the same thing: proxy is for [optimistic
346
- checks](https://nextjs.org/docs/app/guides/authentication#optimistic-checks-with-proxy-optional),
347
- "not ... a full session management or authorization solution."
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
- ConnectAppShell,
519
+ AppShell,
409
520
  ConnectLoginButton, ConnectRegisterButton,
410
- ConnectUserMenu, ConnectAppsGrid, ConnectPlans,
521
+ ConnectUserButton, ConnectAppsGrid, ConnectPlans,
411
522
  } from "@cosmoops/connect-sdk/client";
412
523
 
413
- // The whole frame — sidebar, top bar, access gate. See "App shell" below.
414
- <ConnectAppShell name="Dispatch" nav={nav}>{children}</ConnectAppShell>
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
- <ConnectUserMenu />
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
- | `ConnectAppShell` | **The whole application frame** — collapsible sidebar, top bar with the launcher and account menu, and your pages in the middle. Mounts `ConnectAccessGate` for you. |
429
- | `ConnectSideNav` | The sidebar's nav column on its own — sections, one level of children, active-route highlight — for an app that already has its own chrome. |
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
- | `ConnectUserMenu` | Avatar, name, workspace. Profile, Manage subscription and Open Connect each open Connect in a new tab; Sign out acts in place. Renders nothing when signed out. |
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 service — KYC, payment method, seat headroom — each with the Connect screen that fixes it. |
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({ organizationId, addonId: "addon_sms", quantity: 1 }); // server
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 service's feature catalog.
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 service. |
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(orgId)` and `client.entitlements.hasFeature(orgId, feature)`.
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 { ConnectAppShell } from "@cosmoops/connect-sdk/client";
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
- <ConnectAppShell
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
- title: "Workspace",
717
+ label: "Settings",
718
+ href: "/settings",
719
+ icon: <Settings className="size-5" />,
597
720
  items: [
598
- {
599
- label: "Settings",
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
- </ConnectAppShell>
729
+ </AppShell>
614
730
  );
615
731
  }
616
732
  ```
617
733
 
618
- That is the entire layout of a service platform. Everything that should look
619
- identical across the family — the lockup, the rail geometry, the active-route
620
- treatment, the app launcher, the account menu, the mobile drawer — comes from
621
- here; only `name`, `icon` and `nav` differ per platform.
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.** `ConnectAppShell` is exactly the "wrap
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 collapses to icons** and remembers the choice per browser
650
- (`localStorage`). Drive it yourself with `collapsed` + `onCollapsedChange`,
651
- or pin it open with `collapsible={false}`.
652
- - **Below `md` there is no rail**, only a drawer behind the top bar's menu
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 menu renders nothing without one,
799
+ nothing once a session exists, the account row renders nothing without one,
658
800
  and the launcher renders nothing when signed out.
659
- - `title`, `headerActions`, `sidebarFooter` and `footer` are slots;
660
- `contentClassName` replaces the content pane's default max-width and
661
- padding.
662
- - `theme` / `onThemeChange` pass straight through to `ConnectUserMenu` — the
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
- `ConnectSideNav` is the nav column alone, for an app with chrome it already
666
- likes. `resolveActivePath` and `isNavItemActive` are the same pure functions
667
- the rail highlights with, exported so a breadcrumb or a document title derived
668
- from your nav cannot disagree with it.
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 service 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.
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