@colixsystems/widget-sdk 0.180.1 → 0.181.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/README.md CHANGED
@@ -59,7 +59,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
59
59
  | **DIRECTORY** (`ctx.directory`) | `useDirectory(query)` (optional: query) | `{ users, loading, error, refetch }` | `directory.users.list` — `directory.read:users` |
60
60
  | **DIRECTORY** | `useUsers(query)` (optional: query) | `{ users, loading, error, refetch, invite, deactivate, reactivate, remove, sendPasswordReset }` | `directory.users.*` — `users.read:*` plus the `users.read` (or `users.write`) capability, else `FORBIDDEN` (edits, incl. `sendPasswordReset()`, also `users.write:*`; `remove()` also `users.delete:*`) |
61
61
  | **DIRECTORY** | `useGroups(query)` (optional: query) | `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` | `directory.groups.*` — `groups.read:*` (mutations also `groups.write:*`) |
62
- | **DIRECTORY** | `useInvites(query)` (optional: query) | `{ invites, loading, error, refetch, resend, revoke }` | `directory.invites.*` — `users.write:*` + the SystemAcl `users.write` capability (the whole invite surface, list included). `query` is `{ status?, limit?, offset? }` with `status` ∈ `pending \| accepted \| revoked \| expired \| all` (endpoint default `all`). |
62
+ | **DIRECTORY** | `useInvites(query)` (optional: query) | `{ invites, loading, error, refetch, resend, revoke }` | `directory.invites.*` — `users.write:*` + the SystemAcl `users.write` capability (the whole invite surface, list included). `query` is `{ status?, q?, app_user_id?, limit?, offset? }` with `status` ∈ `pending \| accepted \| revoked \| expired \| open \| all` (endpoint default `all`; `open` = `pending` or `expired`); `q` matches the invite's name or email, `app_user_id` returns the invites reserving that user id. |
63
63
  | **DIRECTORY** | `useBankIdLink()` | `{ linked, available, status, qr, message, startLink, refresh, cancel, unlink, refetchStatus, … }` | `directory.bankid.*` — no scope (JWT-gated self-service) |
64
64
  | **FILESTORE** (`ctx.filestore`) | `usePdfExport({ spaceType, folderId })` (optional: spaceType, folderId) | `{ openPdf, opening, canOpenPdf, exportToPdf, exporting, error, lastExported }` | `ctx.filestore.files.renderPdf` + `ctx.device.pdfViewer`, `ctx.filestore.files.exportPdf` — `files.write:*`. `openPdf(html, { fileName? })` renders the HTML to a PDF server-side and OPENS it (web: PDF tab; native: print / share sheet) without saving. `exportToPdf(html, { fileName?, folderId? })` renders it and saves it as a file (`application/pdf`). Same server-side renderer on web + native. |
65
65
  | **PAYMENTS** (`ctx.payments`) | `usePayments()` | `{ requestPayment, getPayment }` | `ctx.payments.*` — `payments.charge:appUser`. Rejects with `PaymentError { code, message, retryable }`; when `retryable` is `false` show `message` and drop the retry. Charges are accepted ONLY in the currency the workspace sells in — omit `currency` and the platform applies it (a disagreeing literal is a publish-blocking `payment-currency` finding). |
@@ -74,7 +74,18 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
74
74
 
75
75
  ## Status
76
76
 
77
- `v0.180.1` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
77
+ `v0.181.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
78
+
79
+ ### What's new in 0.181.0 (contract 1.142.0)
80
+
81
+ **A `USER` column can point at someone who has been invited but has not joined yet.** Every invite now reserves the id its invitee's user account will have, so a record can be assigned to that person straight away and nothing needs rewriting when they accept.
82
+
83
+ - **`useInvites()` rows carry `app_user_id`** — that reserved id. The `resend()` result carries it too.
84
+ - **`useInvites(query)` takes two more optional keys.** `q` is a case-insensitive substring match on the invite's name or email. `app_user_id` returns only the invites reserving that id, which is how a member-admin widget resolves the person behind a `USER` value before they have joined; an id no invite reserves returns no rows.
85
+ - **`status: 'open'`** is every invite that is neither accepted nor revoked — `pending` or `expired`. A re-invite keeps the reserved id, so `useInvites({ app_user_id, status: 'open' })` is the one invite that still reserves it.
86
+ - **A `USER` column has a third read shape.** A user of the workspace still reads as `{ id, name, email }` and an id that names nobody as `{ id, name: null }`. An invited person who has not joined reads as `{ id, name, invited: true }` — **with no `email`**, so render `name` and treat `email` as optional. Once they accept, the same id reads as an ordinary user and `invited` is gone.
87
+
88
+ Additive: no existing field changed shape, and the scope is unchanged (`users.write:*` plus the `users.write` capability for the whole invite surface).
78
89
 
79
90
  ### What's new in 0.180.1 (contract 1.141.0 — unchanged)
80
91
 
@@ -2114,9 +2125,17 @@ function PendingInvites() {
2114
2125
  }
2115
2126
  ```
2116
2127
 
2117
- Rows are snake_case: `{ id, email, name, group_ids, status, expires_at, accepted_at,
2118
- revoked_at, created_at }`. Trust the server-computed `status` rather than comparing
2119
- `expires_at` against the device clock.
2128
+ Rows are snake_case: `{ id, app_user_id, email, name, group_ids, status, expires_at,
2129
+ accepted_at, revoked_at, created_at }`. Trust the server-computed `status` rather than
2130
+ comparing `expires_at` against the device clock.
2131
+
2132
+ `query` also takes `q` (a case-insensitive substring match on the invite's name or
2133
+ email) and `app_user_id`. Each row's `app_user_id` is the id the invitee's user account
2134
+ will have once they accept. A `USER` column may hold that id before the person has
2135
+ joined — it then reads back as `{ id, name, invited: true }`, with no `email` — and
2136
+ `useInvites({ app_user_id: value.id, status: 'open', limit: 1 })` returns the invite
2137
+ behind it. `status: 'open'` is every invite that is neither accepted nor revoked
2138
+ (`pending` or `expired`); a re-invite keeps the id, so without it earlier invites match too.
2120
2139
 
2121
2140
  **The gate is `users.write`, not `users.read`.** A pending invite exposes the email of
2122
2141
  someone who is not a member yet, so the backend gates the entire invite surface —
package/dist/contract.cjs CHANGED
@@ -1787,9 +1787,18 @@ const HOOKS = [
1787
1787
  "returns the { data, meta } envelope verbatim — the hook unwraps " +
1788
1788
  "res.data; rows are snake_case (email, status, expires_at, …) and " +
1789
1789
  "status is the server-computed pending/accepted/revoked/expired " +
1790
- "value. query is { status?, limit?, offset? } passed verbatim; the " +
1791
- "endpoint defaults to status 'all', so pass { status: 'pending' } " +
1792
- "for the usual outstanding-invite list. The WHOLE invite surface — " +
1790
+ "value. query is { status?, q?, app_user_id?, limit?, offset? } passed " +
1791
+ "verbatim; the endpoint defaults to status 'all', so pass " +
1792
+ "{ status: 'pending' } for the usual outstanding-invite list; " +
1793
+ "status 'open' is every invite neither accepted nor revoked (pending " +
1794
+ "or expired). q is a " +
1795
+ "case-insensitive substring match on the invite's name or email. " +
1796
+ "Each row's app_user_id is the id the invitee's user account will " +
1797
+ "have once they accept; a USER column may already hold it, and " +
1798
+ "{ app_user_id, status: 'open' } returns the invite behind such an id " +
1799
+ "(a re-invite keeps the id, so without the status earlier invites " +
1800
+ "match too; an id no invite reserves returns no rows). The WHOLE " +
1801
+ "invite surface — " +
1793
1802
  "list included — is gated on users.write:* plus the users.write " +
1794
1803
  "SystemAcl capability, because a pending invite exposes the email of " +
1795
1804
  "someone who is not a member yet; a users.read-only caller cannot see " +
@@ -1799,7 +1808,7 @@ const HOOKS = [
1799
1808
  "refetch on success.",
1800
1809
  returnShape: {
1801
1810
  invites:
1802
- "Array<{ id, email, name, group_ids, status, expires_at, accepted_at, revoked_at, created_at }> // snake_case rows; unwrapped from { data, meta }",
1811
+ "Array<{ id, app_user_id, email, name, group_ids, status, expires_at, accepted_at, revoked_at, created_at }> // snake_case rows; unwrapped from { data, meta }",
1803
1812
  loading: "boolean",
1804
1813
  error: "DirectoryError | null // { code, message, retryable }",
1805
1814
  refetch: "() => Promise<void>",
@@ -4767,7 +4776,11 @@ const CONTRACT = deepFreeze({
4767
4776
  // 1.141.0: additive (sc-9346) — inside a section that paints its own fill,
4768
4777
  // `theme.colors.backdrop` is that fill, and `<Button>` re-inks a variant
4769
4778
  // colour that cannot be read against it by the rule its text follows.
4770
- version: "1.141.0",
4779
+ // 1.142.0: additive (sc-9713) — `useInvites(query)` takes `q`,
4780
+ // `app_user_id` and `status: "open"`, and each invite row carries
4781
+ // `app_user_id`: the id the invitee's user account will have once they
4782
+ // accept.
4783
+ version: "1.142.0",
4771
4784
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4772
4785
  hooks: HOOKS,
4773
4786
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -1787,9 +1787,18 @@ const HOOKS = [
1787
1787
  "returns the { data, meta } envelope verbatim — the hook unwraps " +
1788
1788
  "res.data; rows are snake_case (email, status, expires_at, …) and " +
1789
1789
  "status is the server-computed pending/accepted/revoked/expired " +
1790
- "value. query is { status?, limit?, offset? } passed verbatim; the " +
1791
- "endpoint defaults to status 'all', so pass { status: 'pending' } " +
1792
- "for the usual outstanding-invite list. The WHOLE invite surface — " +
1790
+ "value. query is { status?, q?, app_user_id?, limit?, offset? } passed " +
1791
+ "verbatim; the endpoint defaults to status 'all', so pass " +
1792
+ "{ status: 'pending' } for the usual outstanding-invite list; " +
1793
+ "status 'open' is every invite neither accepted nor revoked (pending " +
1794
+ "or expired). q is a " +
1795
+ "case-insensitive substring match on the invite's name or email. " +
1796
+ "Each row's app_user_id is the id the invitee's user account will " +
1797
+ "have once they accept; a USER column may already hold it, and " +
1798
+ "{ app_user_id, status: 'open' } returns the invite behind such an id " +
1799
+ "(a re-invite keeps the id, so without the status earlier invites " +
1800
+ "match too; an id no invite reserves returns no rows). The WHOLE " +
1801
+ "invite surface — " +
1793
1802
  "list included — is gated on users.write:* plus the users.write " +
1794
1803
  "SystemAcl capability, because a pending invite exposes the email of " +
1795
1804
  "someone who is not a member yet; a users.read-only caller cannot see " +
@@ -1799,7 +1808,7 @@ const HOOKS = [
1799
1808
  "refetch on success.",
1800
1809
  returnShape: {
1801
1810
  invites:
1802
- "Array<{ id, email, name, group_ids, status, expires_at, accepted_at, revoked_at, created_at }> // snake_case rows; unwrapped from { data, meta }",
1811
+ "Array<{ id, app_user_id, email, name, group_ids, status, expires_at, accepted_at, revoked_at, created_at }> // snake_case rows; unwrapped from { data, meta }",
1803
1812
  loading: "boolean",
1804
1813
  error: "DirectoryError | null // { code, message, retryable }",
1805
1814
  refetch: "() => Promise<void>",
@@ -4763,7 +4772,11 @@ const CONTRACT = deepFreeze({
4763
4772
  // 1.141.0: additive (sc-9346) — inside a section that paints its own fill,
4764
4773
  // `theme.colors.backdrop` is that fill, and `<Button>` re-inks a variant
4765
4774
  // colour that cannot be read against it by the rule its text follows.
4766
- version: "1.141.0",
4775
+ // 1.142.0: additive (sc-9713) — `useInvites(query)` takes `q`,
4776
+ // `app_user_id` and `status: "open"`, and each invite row carries
4777
+ // `app_user_id`: the id the invitee's user account will have once they
4778
+ // accept.
4779
+ version: "1.142.0",
4767
4780
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4768
4781
  hooks: HOOKS,
4769
4782
  primitives: PRIMITIVES,
package/dist/index.d.ts CHANGED
@@ -2185,6 +2185,12 @@ export type AppUserInviteStatus =
2185
2185
 
2186
2186
  export interface AppUserInviteRow {
2187
2187
  id: string;
2188
+ /**
2189
+ * The id the invitee's user account will have once they accept. On every
2190
+ * `useInvites()` row and the `resend()` result; `useUsers().invite()` does
2191
+ * not return it.
2192
+ */
2193
+ app_user_id?: string;
2188
2194
  email: string;
2189
2195
  /** Server-computed lifecycle value — prefer it over re-deriving expiry. */
2190
2196
  status: AppUserInviteStatus;
@@ -2258,8 +2264,20 @@ export function useGroups(query?: GroupsQuery): GroupsApi;
2258
2264
  // sc-5097 — pending AppUser invite administration.
2259
2265
 
2260
2266
  export interface InvitesQuery {
2261
- /** Defaults to "all" server-side; pass "pending" for outstanding invites. */
2262
- status?: AppUserInviteStatus | "all";
2267
+ /**
2268
+ * Defaults to "all" server-side; pass "pending" for outstanding invites.
2269
+ * "open" is "pending" or "expired": neither accepted nor revoked.
2270
+ */
2271
+ status?: AppUserInviteStatus | "open" | "all";
2272
+ /** Case-insensitive substring match on the invite's name or email. */
2273
+ q?: string;
2274
+ /**
2275
+ * Only the invites reserving this user id (a row's `app_user_id`) — resolves
2276
+ * the person behind an id a USER column holds before they have joined. A
2277
+ * re-invite keeps the id, so add `status: "open"` for the one that still
2278
+ * reserves it. An id no invite reserves returns no rows.
2279
+ */
2280
+ app_user_id?: string;
2263
2281
  limit?: number;
2264
2282
  offset?: number;
2265
2283
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.180.1",
3
+ "version": "0.181.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",