@cosmoops/connect-sdk 0.9.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 +497 -0
- package/README.md +111 -41
- package/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +20 -1
- package/dist/cjs/client/components/app-shell.js +75 -41
- package/dist/cjs/client/components/apps-grid.js +3 -2
- package/dist/cjs/client/components/ask-ai.js +20 -1
- 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 +41 -0
- 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 +49 -23
- 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 +96 -37
- package/dist/cjs/client/components/workspace-switcher.js +100 -0
- package/dist/cjs/client/index.js +69 -2
- package/dist/cjs/client/provider.js +57 -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 +196 -18
- package/dist/cjs/server/http.js +6 -2
- 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/identity.js +7 -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 +20 -1
- package/dist/esm/client/components/app-shell.js +81 -47
- package/dist/esm/client/components/apps-grid.js +3 -2
- package/dist/esm/client/components/ask-ai.js +20 -1
- 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 +40 -0
- 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 +48 -21
- 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 +95 -38
- package/dist/esm/client/components/workspace-switcher.js +97 -0
- package/dist/esm/client/index.js +46 -4
- package/dist/esm/client/provider.js +53 -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 +196 -18
- package/dist/esm/server/http.js +6 -2
- 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/identity.js +7 -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/types/client/components/app-shell.d.ts +65 -27
- package/dist/types/client/components/apps-grid.d.ts +11 -2
- 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 +24 -0
- 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 +27 -13
- package/dist/types/client/components/sidebar.d.ts +95 -0
- package/dist/types/client/components/user-menu.d.ts +68 -22
- package/dist/types/client/components/workspace-switcher.d.ts +49 -0
- package/dist/types/client/index.d.ts +27 -5
- package/dist/types/client/provider.d.ts +109 -6
- 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 +8 -2
- 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/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 +319 -15
- 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 +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,503 @@
|
|
|
3
3
|
`@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
|
|
4
4
|
versions may carry breaking changes** and patch versions never do.
|
|
5
5
|
|
|
6
|
+
## 0.15.0
|
|
7
|
+
|
|
8
|
+
Your application's own database stops being a toy. Real queries, real
|
|
9
|
+
aggregations, and real atomicity - without ever handing you a Firebase
|
|
10
|
+
credential.
|
|
11
|
+
|
|
12
|
+
Everything here is `data.*`. The rest of the SDK is unchanged.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`data.aggregate()` - count, sum and average in the database.**
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const { values } = await connect.data.aggregate({
|
|
20
|
+
workspaceId,
|
|
21
|
+
collection: "deals",
|
|
22
|
+
filters: [{ field: "outcome", op: "==", value: "won" }],
|
|
23
|
+
aggregations: [
|
|
24
|
+
{ op: "count", alias: "count" },
|
|
25
|
+
{ op: "sum", field: "value", alias: "total" },
|
|
26
|
+
],
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Firestore bills an aggregation by the index entries it scans rather than by
|
|
31
|
+
the documents it would have returned, so a dashboard tile over ten thousand
|
|
32
|
+
rows costs a handful of reads and stays flat as the collection grows. Every
|
|
33
|
+
integration that had no aggregate call to reach for was paging the rows into
|
|
34
|
+
its own process and adding them up, which is an unbounded read wearing a
|
|
35
|
+
different name.
|
|
36
|
+
|
|
37
|
+
A `sum` or `avg` over nothing answers `null`, not `0` - "nothing matched"
|
|
38
|
+
and "the total is zero" are different facts.
|
|
39
|
+
|
|
40
|
+
- **`data.commit()` - a set of writes that all land or none do**, across as
|
|
41
|
+
many of your collections as they name.
|
|
42
|
+
|
|
43
|
+
You never hold a transaction: a handle passed across an HTTP boundary is a
|
|
44
|
+
server-side lock whose duration your network controls. Instead you read,
|
|
45
|
+
decide, and commit conditional on nothing having moved. Connect runs the
|
|
46
|
+
whole set as one short transaction on its side.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { increment, unchangedSince } from "@cosmoops/connect-sdk";
|
|
50
|
+
|
|
51
|
+
await connect.data.commit({
|
|
52
|
+
workspaceId,
|
|
53
|
+
operations: [
|
|
54
|
+
{ op: "create", collection: "ledger", id, data: entry },
|
|
55
|
+
{
|
|
56
|
+
op: "merge",
|
|
57
|
+
collection: "funds",
|
|
58
|
+
id: entry.fundId,
|
|
59
|
+
data: { balancePaise: increment(entry.amountPaise) },
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A stale precondition throws `ConnectError` with status 409 and writes
|
|
66
|
+
nothing.
|
|
67
|
+
|
|
68
|
+
- **`data.transaction(build)`** - the read/decide/commit loop with the retry
|
|
69
|
+
written once. `build` re-runs against fresh reads when a precondition went
|
|
70
|
+
stale, and only a 409 is retried: a 403 means the operation was wrong rather
|
|
71
|
+
than late.
|
|
72
|
+
|
|
73
|
+
- **Field transforms**: `increment`, `serverTimestamp`, `arrayUnion`,
|
|
74
|
+
`arrayRemove`, `deleteField`, plus the `unchangedSince(document)` and
|
|
75
|
+
`documentExists()` precondition builders. All exported from the root entry,
|
|
76
|
+
so a Server Component and a client component build the same write the same
|
|
77
|
+
way.
|
|
78
|
+
|
|
79
|
+
`increment` is the one that genuinely cannot be done any other way: it is
|
|
80
|
+
commutative, so two concurrent increments both land - where read-add-write
|
|
81
|
+
from your own process loses one of them however carefully it retries.
|
|
82
|
+
|
|
83
|
+
- **Composite indexes.** Declare them per collection on your application's
|
|
84
|
+
**Store** tab and Connect builds them in your database, reconciling on every
|
|
85
|
+
save - including removing ones you no longer declare, which would otherwise
|
|
86
|
+
be paid for on every document write forever.
|
|
87
|
+
|
|
88
|
+
This is what lifts the old one-filter ceiling. A query with no matching
|
|
89
|
+
index is still refused, but the refusal now names the index to declare
|
|
90
|
+
rather than telling you the shape is impossible.
|
|
91
|
+
|
|
92
|
+
- **`_updateTime`** on every document that came from a read - Firestore's own
|
|
93
|
+
write timestamp, and the value a `{ updateTime }` precondition is compared
|
|
94
|
+
against. Not `_updatedAt`: a transform in the same commit can move that
|
|
95
|
+
field, and cannot touch the write timestamp.
|
|
96
|
+
|
|
97
|
+
### Changed - breaking
|
|
98
|
+
|
|
99
|
+
- **`data.query({ filter })` is now `data.query({ filters })`**, a list, ANDed.
|
|
100
|
+
`data.scan` takes the same. One filter becomes a one-element array:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// Before
|
|
104
|
+
await connect.data.query({ workspaceId, collection: "tickets", filter: f });
|
|
105
|
+
// After
|
|
106
|
+
await connect.data.query({ workspaceId, collection: "tickets", filters: [f] });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The server still accepts the singular `filter` query parameter, so an
|
|
110
|
+
integration on 0.14 or earlier keeps working against an upgraded Connect -
|
|
111
|
+
but the SDK method has moved and this one is a code change.
|
|
112
|
+
|
|
113
|
+
- **`Plan.billingInterval` and `SubscribedPlan.billingInterval` are now
|
|
114
|
+
`BillingInterval`**, which adds `"one-time"`. Connect has been able to
|
|
115
|
+
return it; the SDK type could not represent it, so a one-time plan was typed
|
|
116
|
+
as monthly and rendered "per month" - a false price rather than a cosmetic
|
|
117
|
+
slip. `ConnectPlans`, `ConnectSubscription` and the subscribe dialog now
|
|
118
|
+
label it "once".
|
|
119
|
+
|
|
120
|
+
Exhaustive `switch`es on a billing interval will need a third arm.
|
|
121
|
+
|
|
122
|
+
### Added - operators
|
|
123
|
+
|
|
124
|
+
- `not-in` joins the query operators.
|
|
125
|
+
|
|
126
|
+
## 0.14.0
|
|
127
|
+
|
|
128
|
+
One session answers "who is this?" once, and the subscribe button goes where
|
|
129
|
+
it says it does.
|
|
130
|
+
|
|
131
|
+
### Changed - breaking
|
|
132
|
+
|
|
133
|
+
- **`applicationRole` and `platformAccess` are now `role`, `permissions` and
|
|
134
|
+
`staff`**, three flat fields on `exchangeCode()` and `getUser()` (and on the
|
|
135
|
+
`/session` bridge read).
|
|
136
|
+
|
|
137
|
+
The two blocks they replace were mutually exclusive by construction. A
|
|
138
|
+
Connect platform account belongs to no workspace - Connect enforces it - so
|
|
139
|
+
a member session always carried `platformAccess: null` and a staff session
|
|
140
|
+
always carried `applicationRole: { role: null, granted: [] }`. Two shapes
|
|
141
|
+
for one question meant every integration wrote the same null-check twice,
|
|
142
|
+
and the block that did not apply still had to be built and sent.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// Before
|
|
146
|
+
const { applicationRole, platformAccess } = await connect.identity.getUser(t);
|
|
147
|
+
if (platformAccess) {
|
|
148
|
+
if (!platformAccess.roles.includes("platform_admin")) return forbidden();
|
|
149
|
+
return renderOperatorConsole();
|
|
150
|
+
}
|
|
151
|
+
if (!applicationRole.granted.includes("invoice:write")) return forbidden();
|
|
152
|
+
|
|
153
|
+
// After
|
|
154
|
+
const { role, permissions, staff } = await connect.identity.getUser(t);
|
|
155
|
+
if (staff) {
|
|
156
|
+
if (role !== "platform_admin") return forbidden();
|
|
157
|
+
return renderOperatorConsole();
|
|
158
|
+
}
|
|
159
|
+
if (!permissions.includes("invoice:write")) return forbidden();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Read `staff` first: it decides which vocabulary the other two speak - your
|
|
163
|
+
own Permission Catalog's ids for a member, Connect's `platform:*` ids for an
|
|
164
|
+
operator. A staff session reports one `role` rather than a list, and it is
|
|
165
|
+
the highest-authority role your application accepts; the platform roles nest
|
|
166
|
+
(`developer` ⊆ `platform_manager` ⊆ `platform_admin`), so nothing is lost,
|
|
167
|
+
and `permissions` is still the union over every accepted role.
|
|
168
|
+
|
|
169
|
+
`ApplicationRole` and `PlatformAccess` are gone as exported types, replaced
|
|
170
|
+
by `SessionAccess`. `useConnectPlatformAccess()` keeps its shape -
|
|
171
|
+
`roles` is now a one-element array on a staff session, `[]` otherwise - so
|
|
172
|
+
`<ConnectPlatformGate accept={[...]}>` is unchanged.
|
|
173
|
+
|
|
174
|
+
The signed-out `/session` response carries the same three fields
|
|
175
|
+
(`role: null, permissions: [], staff: false`) rather than a bare
|
|
176
|
+
`applicationRole: null`, so a client never tells "signed out" apart from
|
|
177
|
+
"signed in with no role" by which keys happen to be present.
|
|
178
|
+
|
|
179
|
+
- **Workspace role ids lost their `org_` prefix** - `org_admin` is `admin`,
|
|
180
|
+
and the same for `finance`, `manager` and `member`. This is visible to you
|
|
181
|
+
only through `access.getUserRoles()` and `access.listRoles()`, which report
|
|
182
|
+
a *member's* resolved role on your application rather than these; if you
|
|
183
|
+
hardcoded a Connect workspace role id anywhere, it needs updating. Grants
|
|
184
|
+
stored under the old ids keep resolving.
|
|
185
|
+
|
|
186
|
+
### Fixed
|
|
187
|
+
|
|
188
|
+
- **Subscribing no longer lands on a 404.** `buildCheckoutUrl` - and so
|
|
189
|
+
`<ConnectPlans>`'s subscribe button - built
|
|
190
|
+
`/applications/{id}/subscribe/{planId}`. The portal route customers can
|
|
191
|
+
actually open is `/apps/...`; `/applications` is a Console-only prefix. Every
|
|
192
|
+
first-time subscriber sent from a partner's own pricing page hit a 404. The
|
|
193
|
+
matching `resolveUrl` on the `subscription_missing` readiness blocker was
|
|
194
|
+
wrong the same way and is fixed with it.
|
|
195
|
+
|
|
196
|
+
- **`staff` actually reaches you.** Both `/sso/token` and `/sso/user` resolved
|
|
197
|
+
the staff block, typed it and documented it, then dropped it while building
|
|
198
|
+
the JSON response - the handlers are hand-built rather than schema-parsed,
|
|
199
|
+
so a forgotten field was silent. Staff access has been invisible to every
|
|
200
|
+
partner since it shipped in 0.11.0. If you turned it on and concluded it did
|
|
201
|
+
not work, it did not.
|
|
202
|
+
|
|
203
|
+
- `SDK_VERSION`, sent in the `User-Agent`, said `0.12.0` on a 0.13.0 package.
|
|
204
|
+
|
|
205
|
+
## 0.13.0
|
|
206
|
+
|
|
207
|
+
The platform's grievance channel is now something you read rather than
|
|
208
|
+
something you copy.
|
|
209
|
+
|
|
210
|
+
### Added
|
|
211
|
+
|
|
212
|
+
- **`connect.platform.getCompanyProfile()`** - the registered entity operating
|
|
213
|
+
Connect, and the grievance officer s.13 of India's DPDP Act 2023 requires a
|
|
214
|
+
Data Fiduciary to publish. Backed by the new
|
|
215
|
+
`GET /api/v1/application/company-profile`, which needs the new
|
|
216
|
+
**`company:read`** scope on your API key.
|
|
217
|
+
|
|
218
|
+
Your application is a Data Processor on that notice, not a second Data
|
|
219
|
+
Fiduciary, so the channel your privacy page names is this one. The shape it
|
|
220
|
+
replaces is four lines in an `.env`:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Before - a statutory contact that can go stale without anyone finding out.
|
|
224
|
+
APP_DATA_FIDUCIARY_NAME="..."
|
|
225
|
+
APP_GRIEVANCE_OFFICER_EMAIL="..."
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
An officer resigns, and every application holding a copy is now publishing a
|
|
229
|
+
redressal channel that reaches nobody. Reading it removes that failure mode
|
|
230
|
+
outright, which is the entire reason it is an endpoint.
|
|
231
|
+
|
|
232
|
+
Every field is resolved server-side against the platform's published
|
|
233
|
+
defaults, so only `website` and `gstNumber` are nullable - there is nothing
|
|
234
|
+
here for you to write a fallback for.
|
|
235
|
+
|
|
236
|
+
- **`useConnectCompanyProfile()`** - the same read through the browser bridge.
|
|
237
|
+
The one hook here that does not depend on a session, so it resolves for a
|
|
238
|
+
signed-out visitor too: the moment a grievance channel most needs to be
|
|
239
|
+
readable is on a public privacy page.
|
|
240
|
+
|
|
241
|
+
- **`<ConnectGrievanceNotice />`** - the notice, rendered. The wording lives in
|
|
242
|
+
the SDK rather than being retyped per application, so a user comparing two
|
|
243
|
+
applications' privacy pages does not find two different accounts of who
|
|
244
|
+
their Data Fiduciary is.
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
import { ConnectGrievanceNotice } from "@cosmoops/connect-sdk/client";
|
|
248
|
+
|
|
249
|
+
<ConnectGrievanceNotice applicationName="CosmoOps CRM" />
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
It renders `null` while loading and `null` on failure rather than a skeleton
|
|
253
|
+
or an error box - a red error in the middle of a legal notice reads as the
|
|
254
|
+
notice itself being broken. Call the hook directly if you want those states.
|
|
255
|
+
|
|
256
|
+
### Upgrading
|
|
257
|
+
|
|
258
|
+
Existing API keys do not carry `company:read`. Tick it on the key's scopes in
|
|
259
|
+
the Console's Integration tab, or re-run the seed script for that application,
|
|
260
|
+
which widens scopes in place.
|
|
261
|
+
|
|
262
|
+
## 0.12.0
|
|
263
|
+
|
|
264
|
+
The application shell is one column now. The top bar is gone, the rail holds
|
|
265
|
+
everything it used to, and the rail starts collapsed.
|
|
266
|
+
|
|
267
|
+
### Changed - breaking
|
|
268
|
+
|
|
269
|
+
- **Two renames, both with deprecated aliases kept**, so nothing breaks on
|
|
270
|
+
upgrade:
|
|
271
|
+
|
|
272
|
+
| Was | Now | Why |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| `ConnectSideNav` | `ConnectSidebarNav` | The rail is a `ConnectSidebar` now; its parts are named after it |
|
|
275
|
+
| `ConnectUserMenu` | `ConnectUserButton` | It is a button that opens a modal. "Menu" had people looking for a dropdown that never existed |
|
|
276
|
+
|
|
277
|
+
**`AppShell` keeps its name.** It stays the one export in `/client` without
|
|
278
|
+
the `Connect` prefix, exactly as 0.8.0 decided. A host whose own layout
|
|
279
|
+
component is also called `AppShell` aliases it on import - `import {
|
|
280
|
+
AppShell as ConnectAppShell }` - which is the documented answer to that
|
|
281
|
+
clash and what both reference integrations do.
|
|
282
|
+
|
|
283
|
+
- **The top bar is removed.** With it go two props:
|
|
284
|
+
|
|
285
|
+
- **`title`** - a page's heading belongs to the page. Move it to
|
|
286
|
+
`ConnectPageShell`, from `@cosmoops/connect-sdk/ui`, which renders it next
|
|
287
|
+
to that page's own breadcrumbs and actions:
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
// before // after
|
|
291
|
+
<AppShell title="Billing"> <AppShell>
|
|
292
|
+
<BillingPage /> <ConnectPageShell title="Billing">…</ConnectPageShell>
|
|
293
|
+
</AppShell> </AppShell>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
- **`headerActions`** - there is no header to put them in. A control that
|
|
297
|
+
belongs to a page goes in that page's `ConnectPageShell`; a control that
|
|
298
|
+
belongs to the *application* goes in `sidebarFooter`, which now accepts
|
|
299
|
+
sidebar rows (see `ConnectSidebarRow`).
|
|
300
|
+
|
|
301
|
+
Below `md` a slim bar remains, carrying the drawer trigger and the lockup and
|
|
302
|
+
nothing else. A phone has no rail to hold that trigger.
|
|
303
|
+
|
|
304
|
+
- **`defaultCollapsed` now defaults to `true`.** The icon rail is the resting
|
|
305
|
+
state and expanding is the deliberate act - the rail's job is to be *there*,
|
|
306
|
+
not to be read. A stored preference from the user's last visit still wins.
|
|
307
|
+
Pass `defaultCollapsed={false}` to keep 0.11's behaviour.
|
|
308
|
+
|
|
309
|
+
- **`ConnectUserMenu`'s `compact` prop** is superseded by
|
|
310
|
+
`ConnectUserButton`'s `variant`. The alias maps `compact` to
|
|
311
|
+
`variant="compact"` and the old default to `variant="inline"`, so existing
|
|
312
|
+
call sites are unchanged.
|
|
313
|
+
|
|
314
|
+
- **`ConnectAppsGrid` now renders as a sidebar row by default**
|
|
315
|
+
(`variant="row"`). Pass `variant="icon"` for the square accent button it used
|
|
316
|
+
to be.
|
|
317
|
+
|
|
318
|
+
### Added
|
|
319
|
+
|
|
320
|
+
- **`secondaryNav`** on `AppShell` - a second `ConnectNav`, pinned to
|
|
321
|
+
the foot of the rail above the account row. For the routes a product has but
|
|
322
|
+
does not want competing with its own: settings, help, plans, status. Same
|
|
323
|
+
shape, same active resolution, same audience filtering, same `gateExempt`
|
|
324
|
+
handling as `nav`.
|
|
325
|
+
|
|
326
|
+
- **The workspace switcher moved into the rail's header**, directly under the
|
|
327
|
+
brand, and is now collapse-aware: a 32px initials tile when the rail is
|
|
328
|
+
narrow, tile plus name plus chevron when it is open. It still hides itself
|
|
329
|
+
for an account with one workspace.
|
|
330
|
+
|
|
331
|
+
- **The account row is the last thing in the rail.** Circular avatar, name over
|
|
332
|
+
email, opening the same account modal as before. It collapses to the avatar
|
|
333
|
+
alone, which is still a complete account control.
|
|
334
|
+
|
|
335
|
+
- **`ConnectProfileModal`** - the account modal with no trigger of its own,
|
|
336
|
+
controlled by `isOpen`/`onOpenChange`. For an application that wants to keep
|
|
337
|
+
its own account control and put Connect's modal behind it.
|
|
338
|
+
|
|
339
|
+
- **The rail's pieces are exported**: `ConnectSidebar`, `ConnectSidebarHeader`,
|
|
340
|
+
`ConnectSidebarBody`, `ConnectSidebarFooter`, `ConnectSidebarRow`, plus
|
|
341
|
+
`ConnectShellProvider` / `useConnectShell` / `sidebarRowClass` and the
|
|
342
|
+
`RAIL_COLLAPSED` / `RAIL_EXPANDED` constants. `AppShell` composes
|
|
343
|
+
exactly these, so an application whose chrome the prop API cannot express
|
|
344
|
+
rebuilds the same rail from the same parts rather than forking it.
|
|
345
|
+
|
|
346
|
+
- **`ConnectLogo` takes `variant`** - `"full"` | `"mark"` | `"wordmark"`.
|
|
347
|
+
`collapsed` is deprecated in favour of `variant="mark"`.
|
|
348
|
+
|
|
349
|
+
### Fixed
|
|
350
|
+
|
|
351
|
+
- **The collapsed Ask AI panel occupied 400px at `lg` and up.** Its shell
|
|
352
|
+
carried `lg:w-[400px]` and `lg:w-0` at once; Tailwind emits arbitrary values
|
|
353
|
+
after scale values, so the override never applied and the closed panel sat
|
|
354
|
+
permanently in the layout, eating that much of the content pane on every page
|
|
355
|
+
with `askAi` on. Only the `translate-x` hid it below `lg`, which is why it
|
|
356
|
+
looked right on a phone and wrong on a laptop.
|
|
357
|
+
|
|
358
|
+
- **Sidebar contrast, in both directions.** Resting nav rows were
|
|
359
|
+
`--foreground` - 17.6:1 against the rail in light, 15.8:1 in dark - so eight
|
|
360
|
+
destinations shouted as loudly as body copy and the selected row had to win
|
|
361
|
+
on background alone. They are `--muted` now (4.8:1 / 6.2:1, past AA), with
|
|
362
|
+
`--foreground` on hover. The *active* row was `text-accent` on
|
|
363
|
+
`bg-accent-soft`, which measures **2.8:1 in light and 4.0:1 in dark - both
|
|
364
|
+
below AA**, on the one row a user most needs to read; it now uses HeroUI's
|
|
365
|
+
derived `--accent-soft-foreground` (4.8:1 / 5.6:1), so a host that re-themes
|
|
366
|
+
`--accent` keeps the contrast.
|
|
367
|
+
|
|
368
|
+
- **The account row and the app launcher looked permanently selected.** Both
|
|
369
|
+
are HeroUI `<Button variant="tertiary">` because they trigger overlays, and
|
|
370
|
+
`.button--tertiary` sets a real `--button-bg`. Next to the plain anchors the
|
|
371
|
+
nav renders, three rows in the rail appeared highlighted at once.
|
|
372
|
+
|
|
373
|
+
- **The account avatar is a circle.** HeroUI's `.avatar` is `rounded-2xl`/
|
|
374
|
+
`rounded-3xl` - right for the media component, wrong for a person, and it
|
|
375
|
+
read as a rendering fault beside circular status dots.
|
|
376
|
+
|
|
377
|
+
### Motion
|
|
378
|
+
|
|
379
|
+
The rail animates its **width and nothing else**, via `motion`. Every row is
|
|
380
|
+
laid out so its glyph sits on the same 32px horizontal centre in both states,
|
|
381
|
+
and every label is clipped by the shrinking rail rather than re-measured - so
|
|
382
|
+
collapsing costs no reflow on any row, and no child moves sideways. Section
|
|
383
|
+
headings hold their box and cross-fade a rule in place of their text, so
|
|
384
|
+
nothing jumps vertically either. A tween rather than a spring: an overshooting
|
|
385
|
+
*width* drags every clip edge past its resting place and back, which reads as
|
|
386
|
+
the labels wobbling.
|
|
387
|
+
|
|
388
|
+
## 0.11.0
|
|
389
|
+
|
|
390
|
+
### Changed - breaking
|
|
391
|
+
|
|
392
|
+
- **`ConnectUser.workspaceId`, `workspaceName` and `workspaceType` are now
|
|
393
|
+
nullable.** They are `null` for exactly one thing: a **Connect platform
|
|
394
|
+
staff** session, described below. Every member session carries them as
|
|
395
|
+
before.
|
|
396
|
+
|
|
397
|
+
If you never turn staff access on for your application - it is off by
|
|
398
|
+
default, and only a Connect platform admin can turn it on - this is a type
|
|
399
|
+
narrow and nothing else. Reading `user.workspaceId` where a `string` is
|
|
400
|
+
wanted becomes a compile error, and `?? ""` is the wrong fix: decide what
|
|
401
|
+
your tenanting does with a session that has no tenant before you enable the
|
|
402
|
+
feature.
|
|
403
|
+
|
|
404
|
+
The honest alternative was a synthesized placeholder workspace, which would
|
|
405
|
+
have kept the types quiet while flowing a fabricated tenant id into the ID
|
|
406
|
+
token's `org_id` claim and into whatever you key your own rows on. A
|
|
407
|
+
compile error is cheaper than that.
|
|
408
|
+
|
|
409
|
+
### Added
|
|
410
|
+
|
|
411
|
+
- **Connect platform staff sign-in.** Connect's own operators
|
|
412
|
+
(`platform_admin`, `platform_manager`, `developer`) can now sign into your
|
|
413
|
+
application, so an operator console can live inside your product and be
|
|
414
|
+
authenticated by Connect rather than by a second login you maintain.
|
|
415
|
+
|
|
416
|
+
These accounts belong to no workspace - Connect enforces that, they cannot
|
|
417
|
+
own or join one - which is why the session is shaped differently: no
|
|
418
|
+
`workspaceId`, `subscription: null`, and `applicationRole.granted: []`.
|
|
419
|
+
Platform authority is deliberately *not* reported through the member-role
|
|
420
|
+
field. What staff hold is the new block:
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
const { platformAccess } = await client.identity.getUser(sessionToken);
|
|
424
|
+
if (!platformAccess) return forbidden();
|
|
425
|
+
if (!platformAccess.roles.includes("platform_admin")) return forbidden();
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
`roles` is the intersection of what the account holds and what your
|
|
429
|
+
application accepts - never Connect's full role list for that person.
|
|
430
|
+
|
|
431
|
+
Off by default and per-application. Ask a Connect platform admin to enable it
|
|
432
|
+
on your catalogue entry, naming the roles you accept; until then
|
|
433
|
+
`platformAccess` is always `null` and nothing below does anything.
|
|
434
|
+
|
|
435
|
+
- **`platformAccess` on `exchangeCode()` and `getUser()`** - `PlatformAccess |
|
|
436
|
+
null`. Live on `getUser()`, like `subscription` and `applicationRole`: a
|
|
437
|
+
platform role revoked, or staff access turned off for your application,
|
|
438
|
+
comes back `null` there and Connect has already revoked the session.
|
|
439
|
+
|
|
440
|
+
- **`<ConnectPlatformGate>`** and **`useConnectPlatformAccess()`** in
|
|
441
|
+
`/client`. The gate renders its children only for a staff session, and takes
|
|
442
|
+
an optional `roles` allowlist when your console has tiers of its own.
|
|
443
|
+
|
|
444
|
+
Like every gate in this package it is a **UX gate, not a security
|
|
445
|
+
boundary** - and that matters more here than anywhere else, because what
|
|
446
|
+
sits behind it is usually an operator console. Re-check `platformAccess`
|
|
447
|
+
server-side on every route it reveals.
|
|
448
|
+
|
|
449
|
+
- **`ConnectNavItem.audience`** - `"staff"` or `"member"`. `AppShell` filters
|
|
450
|
+
the rail by it off the live session, so your operator routes appear only for
|
|
451
|
+
staff and your billing/team/usage routes disappear for them. A parent whose
|
|
452
|
+
children all disappear is dropped with them. `filterNavForAudience` is
|
|
453
|
+
exported for a rail you render yourself.
|
|
454
|
+
|
|
455
|
+
- `ConnectAccessGate` passes a staff session straight through. Every state it
|
|
456
|
+
reports is a fact about a workspace, and `subscription_missing` would
|
|
457
|
+
otherwise send an operator to your plan picker to buy a plan for a workspace
|
|
458
|
+
that does not exist.
|
|
459
|
+
|
|
460
|
+
### Note
|
|
461
|
+
|
|
462
|
+
Workspace-scoped bridge routes (`/entitlements`, `/addons`, `/integrations`,
|
|
463
|
+
`/ai/chat`, `/data/*`, `/kv/*`) answer `403 no_workspace` for a staff session.
|
|
464
|
+
There is no tenant to read, and the only other option would be letting the
|
|
465
|
+
request name one - which is precisely what `createConnectHandler` exists to
|
|
466
|
+
prevent. An operator console reads `platformAccess` and talks to your own
|
|
467
|
+
backend for anything tenant-shaped.
|
|
468
|
+
|
|
469
|
+
## 0.10.0
|
|
470
|
+
|
|
471
|
+
### Added
|
|
472
|
+
|
|
473
|
+
- **`@cosmoops/connect-sdk/ui`** - the page-composition layer, and a new entry
|
|
474
|
+
point. `ConnectPageShell` (with `.Header` and `.Async`), `ConnectPageHeader`,
|
|
475
|
+
`ConnectSection`, `ConnectEmptyState`, `ConnectSettingRow`,
|
|
476
|
+
`ConnectStatusChip`.
|
|
477
|
+
|
|
478
|
+
The SDK already shipped the *application* chrome - the shell, the launcher,
|
|
479
|
+
the account menu, the plan grid - and nothing between that and raw HeroUI. So
|
|
480
|
+
every integration re-derived the same six primitives, and they drifted the
|
|
481
|
+
same way each time: the page action ending up below the description instead
|
|
482
|
+
of beside the title, section headings rendered as `h2` so every block became
|
|
483
|
+
a sibling of its own page, and one empty state doing duty for both "you have
|
|
484
|
+
none" and "none match this filter". Those are the three bugs these components
|
|
485
|
+
are shaped to make unrepresentable, not merely to style consistently.
|
|
486
|
+
|
|
487
|
+
**A separate entry from `/client`, deliberately.** That module's barrel
|
|
488
|
+
carries a `"use client"` directive, so re-exporting a presentational `<Card>`
|
|
489
|
+
wrapper through it would turn every page shell into a client boundary. Nothing
|
|
490
|
+
in `/ui` has a hook or state, so it renders in a Server Component and stays
|
|
491
|
+
out of the browser bundle.
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
import { ConnectPageShell, ConnectSection } from "@cosmoops/connect-sdk/ui";
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
- **`ConnectConfirmDialog`, `ConnectCursorPagination`, `ConnectFilterTabs`** in
|
|
498
|
+
`@cosmoops/connect-sdk/client` - the interactive counterparts, which hold
|
|
499
|
+
state and so belong on the browser side. `ConnectCursorPagination` has no page
|
|
500
|
+
numbers on purpose: a keyset-paginated list cannot know its page count without
|
|
501
|
+
a second billed index scan over the same filter, for a number nobody acts on.
|
|
502
|
+
|
|
6
503
|
## 0.9.0
|
|
7
504
|
|
|
8
505
|
### Added
|