@colixsystems/widget-sdk 0.213.0 → 0.214.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
@@ -83,7 +83,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
83
83
 
84
84
  ## Status
85
85
 
86
- `v0.213.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**.
86
+ `v0.214.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**.
87
+
88
+ ### What's new in 0.214.0 (contract 1.173.0)
89
+
90
+ **Who is behind a figure — `useActiveUsers({ within, group_id, limit, offset })`.** One page of the users seen within `within` — `"online"` (default; the app open in the last two minutes), `"day"` (since midnight in the workspace's time zone), `"week"` or `"month"` (the last 7 / 30 days) — most recently seen first, as `[{ id, name, last_active_at, last_platform }]` with `total` for paging. Same gate as `useUserActivity` (`users.activity:*` plus the "View user activity" permission); pass `null` to read nothing.
91
+
92
+ **"Who joined" — `useUsers({ joined_since, sort: "newest" })`.** The admin user list narrows to users created since `joined_since` (a `YYYY-MM-DD` date read in the workspace's time zone, or an ISO instant) and lists them newest first. Join dates are admin data, so a caller with only the directory view gets the plain list.
93
+
94
+ **Reminding several — `remind(id, message, { refetch: false })`.** `useInactiveUsers`' `remind` re-reads the page after each reminder; pass `{ refetch: false }` while sending a batch and call `refetch()` once at the end.
87
95
 
88
96
  ### What's new in 0.213.0 (contract 1.172.0)
89
97
 
@@ -246,7 +254,7 @@ Like the `loading` scope, `alert` styles an SDK component rather than a widget's
246
254
 
247
255
  **An app's user totals for its own admins — `useUserStats({ days, group_id })`.** Active and deactivated users, new users per day (7 / 30 days) or week (90 days) with the previous period's total beside them, users in no group, empty groups, and invite figures. Totals only — never a list of people. Needs `users.read:*` and the viewer's admin user access (else `error.code === "FORBIDDEN"`); `stats.invites` is null unless the widget also declares `users.write:*` and the viewer holds it.
248
256
 
249
- **User activity — `useUserActivity({ days, group_id })` and `useInactiveUsers({ inactive_days, group_id, limit, offset })`.** For a workspace that records user activity: who is online now, active in the last day / week / month, never active, active users per day or week, the web / iOS / Android split and the active share per group — and a page of the users not seen recently, never-seen first. Needs `users.activity:*` and the viewer's "View user activity" permission (else `error.code === "FORBIDDEN"`); when the workspace does not record activity, `error.reason === "ACTIVITY_DISABLED"` — show that, not zeros. Before user activity is offered to the workspace at all, `error.reason` is `"ACTIVITY_UNAVAILABLE"`: hide the section, as for `FORBIDDEN`. `useInactiveUsers` also returns `remind(userId, { title, body })`, which notifies one of them and records when (each row carries `last_reminded_at`). Days follow the workspace's time zone (`period.time_zone`).
257
+ **User activity — `useUserActivity({ days, group_id })` and `useInactiveUsers({ inactive_days, group_id, limit, offset })`.** For a workspace that records user activity: who is online now, active in the last day / week / month, never active, active users per day or week, the web / iOS / Android split and the active share per group — and a page of the users not seen recently, never-seen first. Needs `users.activity:*` and the viewer's "View user activity" permission (else `error.code === "FORBIDDEN"`); when the workspace does not record activity, `error.reason === "ACTIVITY_DISABLED"` — show that, not zeros. Before user activity is offered to the workspace at all, `error.reason` is `"ACTIVITY_UNAVAILABLE"`: hide the section, as for `FORBIDDEN`. `useInactiveUsers` also returns `remind(userId, { title, body })`, which notifies one of them and records when (each row carries `last_reminded_at`); pass `{ refetch: false }` as a third argument when reminding several, then call `refetch()` once. Days follow the workspace's time zone (`period.time_zone`).
250
258
 
251
259
  **Linter fix.** A widget that uses `useGroups` or `useUsers` is no longer refused for a write it never makes: `StyleSheet.create(...)` and an event subscription's bare `remove()` (an `AppState` listener, say) are not user or group writes.
252
260
 
package/dist/contract.cjs CHANGED
@@ -1960,7 +1960,9 @@ const HOOKS = [
1960
1960
  "Returns { users, loading, error, refetch, invite, deactivate, " +
1961
1961
  "reactivate, remove, sendPasswordReset, update }. " +
1962
1962
  "list returns the { data, meta } envelope verbatim — the hook unwraps " +
1963
- "res.data; rows are snake_case (is_active, …). Reads need users.read:* " +
1963
+ "res.data; rows are snake_case (is_active, created_at, …). For an admin, " +
1964
+ "query { joined_since, sort: \"newest\" } lists who joined since a date " +
1965
+ "(read in the workspace's time zone), newest first. Reads need users.read:* " +
1964
1966
  "scope; edit-style mutations (invite/deactivate/reactivate/" +
1965
1967
  "sendPasswordReset/update) need users.write:*, and the destructive remove() " +
1966
1968
  "additionally needs users.delete:* (SC-902). The `invite` call accepts " +
@@ -2082,6 +2084,30 @@ const HOOKS = [
2082
2084
  requiredContextSlice: ["directory.users"],
2083
2085
  scopes: ["users.activity:*"],
2084
2086
  },
2087
+ // sc-11066 — who was seen in a window, behind users.activity.
2088
+ {
2089
+ name: "useActiveUsers",
2090
+ signature: "useActiveUsers({ within, group_id, limit, offset } | null)",
2091
+ optionalArgs: ["within", "group_id", "limit", "offset"],
2092
+ description:
2093
+ "One page of users seen within `within`: \"online\" (default; the app " +
2094
+ "open in the last two minutes), \"day\" (since midnight in the " +
2095
+ "workspace's time zone), \"week\" or \"month\" (the last 7 / 30 days), " +
2096
+ "most recently seen first: `users` = [{ id, name, last_active_at, " +
2097
+ "last_platform }], `total` for paging. Use it to show WHO is behind an " +
2098
+ "online or active figure. Same gate and errors as useUserActivity; null " +
2099
+ "reads nothing.",
2100
+ returnShape: {
2101
+ users: "Array<{ id, name, last_active_at, last_platform }>",
2102
+ total: "number | null",
2103
+ loading: "boolean",
2104
+ fetching: "boolean",
2105
+ error: "DirectoryError | null",
2106
+ refetch: "() => Promise<void>",
2107
+ },
2108
+ requiredContextSlice: ["directory.users"],
2109
+ scopes: ["users.activity:*"],
2110
+ },
2085
2111
  {
2086
2112
  name: "useInactiveUsers",
2087
2113
  signature: "useInactiveUsers({ inactive_days, group_id, limit, offset } | null)",
@@ -2091,7 +2117,8 @@ const HOOKS = [
2091
2117
  "never-seen first, then least recently seen: `users` = [{ id, name, " +
2092
2118
  "last_active_at, last_reminded_at }] (last_active_at null = never seen " +
2093
2119
  "since recording began), `total` for paging. `remind(id, { title, body })` " +
2094
- "notifies one of them, records when (last_reminded_at) and re-reads. " +
2120
+ "notifies one of them, records when (last_reminded_at) and re-reads; " +
2121
+ "pass `{ refetch: false }` third to skip the re-read in a batch. " +
2095
2122
  "Same gate and errors as useUserActivity; null reads nothing.",
2096
2123
  returnShape: {
2097
2124
  users: "Array<{ id, name, last_active_at, last_reminded_at }>",
@@ -2100,7 +2127,7 @@ const HOOKS = [
2100
2127
  fetching: "boolean",
2101
2128
  error: "DirectoryError | null",
2102
2129
  refetch: "() => Promise<void>",
2103
- remind: "(id, { title, body }) => Promise<void>",
2130
+ remind: "(id, { title, body }, { refetch? }) => Promise<void>",
2104
2131
  },
2105
2132
  requiredContextSlice: ["directory.users"],
2106
2133
  scopes: ["users.activity:*"],
@@ -5640,7 +5667,11 @@ const CONTRACT = deepFreeze({
5640
5667
  // and cancel labels, `tone` "default" | "destructive", `busy`). No new
5641
5668
  // theme scope: the panel reads `modal`, the actions `button` and
5642
5669
  // `destructive`. `Button` forwards a ref to its pressable.
5643
- version: "1.172.0",
5670
+ // 1.173.0: additive (sc-11066) — `useActiveUsers(query)` lists who was
5671
+ // seen online / today / in 7 or 30 days through the new
5672
+ // `ctx.directory.users.active`; `useUsers` takes `joined_since` and
5673
+ // `sort: "newest"` for an admin's "who joined" list.
5674
+ version: "1.173.0",
5644
5675
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5645
5676
  hooks: HOOKS,
5646
5677
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -1960,7 +1960,9 @@ const HOOKS = [
1960
1960
  "Returns { users, loading, error, refetch, invite, deactivate, " +
1961
1961
  "reactivate, remove, sendPasswordReset, update }. " +
1962
1962
  "list returns the { data, meta } envelope verbatim — the hook unwraps " +
1963
- "res.data; rows are snake_case (is_active, …). Reads need users.read:* " +
1963
+ "res.data; rows are snake_case (is_active, created_at, …). For an admin, " +
1964
+ "query { joined_since, sort: \"newest\" } lists who joined since a date " +
1965
+ "(read in the workspace's time zone), newest first. Reads need users.read:* " +
1964
1966
  "scope; edit-style mutations (invite/deactivate/reactivate/" +
1965
1967
  "sendPasswordReset/update) need users.write:*, and the destructive remove() " +
1966
1968
  "additionally needs users.delete:* (SC-902). The `invite` call accepts " +
@@ -2082,6 +2084,30 @@ const HOOKS = [
2082
2084
  requiredContextSlice: ["directory.users"],
2083
2085
  scopes: ["users.activity:*"],
2084
2086
  },
2087
+ // sc-11066 — who was seen in a window, behind users.activity.
2088
+ {
2089
+ name: "useActiveUsers",
2090
+ signature: "useActiveUsers({ within, group_id, limit, offset } | null)",
2091
+ optionalArgs: ["within", "group_id", "limit", "offset"],
2092
+ description:
2093
+ "One page of users seen within `within`: \"online\" (default; the app " +
2094
+ "open in the last two minutes), \"day\" (since midnight in the " +
2095
+ "workspace's time zone), \"week\" or \"month\" (the last 7 / 30 days), " +
2096
+ "most recently seen first: `users` = [{ id, name, last_active_at, " +
2097
+ "last_platform }], `total` for paging. Use it to show WHO is behind an " +
2098
+ "online or active figure. Same gate and errors as useUserActivity; null " +
2099
+ "reads nothing.",
2100
+ returnShape: {
2101
+ users: "Array<{ id, name, last_active_at, last_platform }>",
2102
+ total: "number | null",
2103
+ loading: "boolean",
2104
+ fetching: "boolean",
2105
+ error: "DirectoryError | null",
2106
+ refetch: "() => Promise<void>",
2107
+ },
2108
+ requiredContextSlice: ["directory.users"],
2109
+ scopes: ["users.activity:*"],
2110
+ },
2085
2111
  {
2086
2112
  name: "useInactiveUsers",
2087
2113
  signature: "useInactiveUsers({ inactive_days, group_id, limit, offset } | null)",
@@ -2091,7 +2117,8 @@ const HOOKS = [
2091
2117
  "never-seen first, then least recently seen: `users` = [{ id, name, " +
2092
2118
  "last_active_at, last_reminded_at }] (last_active_at null = never seen " +
2093
2119
  "since recording began), `total` for paging. `remind(id, { title, body })` " +
2094
- "notifies one of them, records when (last_reminded_at) and re-reads. " +
2120
+ "notifies one of them, records when (last_reminded_at) and re-reads; " +
2121
+ "pass `{ refetch: false }` third to skip the re-read in a batch. " +
2095
2122
  "Same gate and errors as useUserActivity; null reads nothing.",
2096
2123
  returnShape: {
2097
2124
  users: "Array<{ id, name, last_active_at, last_reminded_at }>",
@@ -2100,7 +2127,7 @@ const HOOKS = [
2100
2127
  fetching: "boolean",
2101
2128
  error: "DirectoryError | null",
2102
2129
  refetch: "() => Promise<void>",
2103
- remind: "(id, { title, body }) => Promise<void>",
2130
+ remind: "(id, { title, body }, { refetch? }) => Promise<void>",
2104
2131
  },
2105
2132
  requiredContextSlice: ["directory.users"],
2106
2133
  scopes: ["users.activity:*"],
@@ -5636,7 +5663,11 @@ const CONTRACT = deepFreeze({
5636
5663
  // and cancel labels, `tone` "default" | "destructive", `busy`). No new
5637
5664
  // theme scope: the panel reads `modal`, the actions `button` and
5638
5665
  // `destructive`. `Button` forwards a ref to its pressable.
5639
- version: "1.172.0",
5666
+ // 1.173.0: additive (sc-11066) — `useActiveUsers(query)` lists who was
5667
+ // seen online / today / in 7 or 30 days through the new
5668
+ // `ctx.directory.users.active`; `useUsers` takes `joined_since` and
5669
+ // `sort: "newest"` for an admin's "who joined" list.
5670
+ version: "1.173.0",
5640
5671
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5641
5672
  hooks: HOOKS,
5642
5673
  primitives: PRIMITIVES,
package/dist/index.d.ts CHANGED
@@ -1107,6 +1107,21 @@ export function useUserActivity(query?: { days?: 7 | 30 | 90; group_id?: string
1107
1107
  refetch(): Promise<void>;
1108
1108
  };
1109
1109
 
1110
+ /** Users seen within `within` (default "online"), most recently seen first. */
1111
+ export function useActiveUsers(query?: {
1112
+ within?: "online" | "day" | "week" | "month";
1113
+ group_id?: string;
1114
+ limit?: number;
1115
+ offset?: number;
1116
+ } | null): {
1117
+ users: Array<{ id: string; name: string; last_active_at: string; last_platform: "web" | "ios" | "android" | "unknown" }>;
1118
+ total: number | null;
1119
+ loading: boolean;
1120
+ fetching: boolean;
1121
+ error: Error | null;
1122
+ refetch(): Promise<void>;
1123
+ };
1124
+
1110
1125
  /** Users not seen for `inactive_days` (default 30), never-seen first. */
1111
1126
  export function useInactiveUsers(query?: {
1112
1127
  inactive_days?: number;
@@ -1121,7 +1136,8 @@ export function useInactiveUsers(query?: {
1121
1136
  error: Error | null;
1122
1137
  refetch(): Promise<void>;
1123
1138
  /** Notify one user, record when, then re-read the page. */
1124
- remind(id: string, message: { title: string; body: string }): Promise<void>;
1139
+ /** `{ refetch: false }` skips re-reading the page, for a batch that reads once. */
1140
+ remind(id: string, message: { title: string; body: string }, options?: { refetch?: boolean }): Promise<void>;
1125
1141
  };
1126
1142
 
1127
1143
  /** One record's online members, as `useOnlineMembers` reports them. */
@@ -2743,6 +2759,12 @@ export interface UsersQuery {
2743
2759
  is_active?: boolean;
2744
2760
  /** Restrict to members of one app-user group — the `id` of a `useGroups()` row. */
2745
2761
  group_id?: string;
2762
+ /** Admin view only: users created since this date (workspace time zone) or instant. */
2763
+ joined_since?: string;
2764
+ /** Admin view only: "newest" lists the most recently created first. */
2765
+ sort?: "name" | "newest";
2766
+ /** "real" leaves out preview and store-review accounts; an in-app admin always gets "real". */
2767
+ audience?: "all" | "real" | "preview";
2746
2768
  limit?: number;
2747
2769
  offset?: number;
2748
2770
  }
package/dist/index.js CHANGED
@@ -127,6 +127,7 @@ export { useDatastoreFigure } from "./datastore-figure.js";
127
127
  export {
128
128
  useUserStats,
129
129
  useUserActivity,
130
+ useActiveUsers,
130
131
  useInactiveUsers,
131
132
  } from "./stats-hooks.js";
132
133
  // sc-11025 — one source, bound to how each platform says the app is in front.
@@ -125,6 +125,7 @@ export { useDatastoreFigure } from "./datastore-figure.js";
125
125
  export {
126
126
  useUserStats,
127
127
  useUserActivity,
128
+ useActiveUsers,
128
129
  useInactiveUsers,
129
130
  } from "./stats-hooks.js";
130
131
  // sc-11025 — see the note on the web mirror in ./index.js.
@@ -108,13 +108,32 @@ export function useUserActivity(query) {
108
108
  return { ...state, activity: data };
109
109
  }
110
110
 
111
+ /**
112
+ * sc-11066 — one page of users seen within `within` ("online" — the last two
113
+ * minutes — "day", "week" or "month"), most recently seen first. `query`:
114
+ * `{ within?, group_id?, limit?, offset? }`. Returns `{ users, total, loading,
115
+ * fetching, error, refetch }`; each user is `{ id, name, last_active_at,
116
+ * last_platform }`. Same gate as useUserActivity; null reads nothing.
117
+ */
118
+ export function useActiveUsers(query) {
119
+ const users = useUsersNamespace("useActiveUsers", "active");
120
+ const key = query === null ? null : serialize(query);
121
+ const { data, ...state } = useKeyedRead(() => users.active(query), "active-users", key, toDirectoryError);
122
+ return {
123
+ ...state,
124
+ users: data && Array.isArray(data.data) ? data.data : [],
125
+ total: data && data.meta && Number.isFinite(data.meta.total) ? data.meta.total : null,
126
+ };
127
+ }
128
+
111
129
  /**
112
130
  * sc-10691 — one page of users not seen for `inactive_days` (default 30),
113
131
  * never-seen first. `query`: `{ inactive_days?, group_id?, limit?, offset? }`.
114
132
  * Returns `{ users, total, loading, fetching, error, refetch, remind }`; each
115
133
  * user is `{ id, name, last_active_at, last_reminded_at }` (last_active_at
116
134
  * null when never seen). `remind(id, { title, body })` notifies one and
117
- * re-reads the page. Same gate as useUserActivity.
135
+ * re-reads the page; `{ refetch: false }` as a third argument skips the
136
+ * re-read, so a batch reads once at the end. Same gate as useUserActivity.
118
137
  */
119
138
  export function useInactiveUsers(query) {
120
139
  const users = useUsersNamespace("useInactiveUsers", "inactive");
@@ -122,7 +141,7 @@ export function useInactiveUsers(query) {
122
141
  const { data, ...state } = useKeyedRead(() => users.inactive(query), "inactive", key, toDirectoryError);
123
142
  const { refetch } = state;
124
143
  const remind = useCallback(
125
- async (id, message) => {
144
+ async (id, message, { refetch: reread = true } = {}) => {
126
145
  if (typeof users.remind !== "function") {
127
146
  throw new Error("useInactiveUsers: host did not inject ctx.directory.users.remind");
128
147
  }
@@ -131,7 +150,7 @@ export function useInactiveUsers(query) {
131
150
  } catch (err) {
132
151
  throw toDirectoryError(err);
133
152
  }
134
- await refetch();
153
+ if (reread) await refetch();
135
154
  },
136
155
  [users, refetch],
137
156
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.213.0",
3
+ "version": "0.214.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",
package/src/contract.js CHANGED
@@ -1960,7 +1960,9 @@ const HOOKS = [
1960
1960
  "Returns { users, loading, error, refetch, invite, deactivate, " +
1961
1961
  "reactivate, remove, sendPasswordReset, update }. " +
1962
1962
  "list returns the { data, meta } envelope verbatim — the hook unwraps " +
1963
- "res.data; rows are snake_case (is_active, …). Reads need users.read:* " +
1963
+ "res.data; rows are snake_case (is_active, created_at, …). For an admin, " +
1964
+ "query { joined_since, sort: \"newest\" } lists who joined since a date " +
1965
+ "(read in the workspace's time zone), newest first. Reads need users.read:* " +
1964
1966
  "scope; edit-style mutations (invite/deactivate/reactivate/" +
1965
1967
  "sendPasswordReset/update) need users.write:*, and the destructive remove() " +
1966
1968
  "additionally needs users.delete:* (SC-902). The `invite` call accepts " +
@@ -2082,6 +2084,30 @@ const HOOKS = [
2082
2084
  requiredContextSlice: ["directory.users"],
2083
2085
  scopes: ["users.activity:*"],
2084
2086
  },
2087
+ // sc-11066 — who was seen in a window, behind users.activity.
2088
+ {
2089
+ name: "useActiveUsers",
2090
+ signature: "useActiveUsers({ within, group_id, limit, offset } | null)",
2091
+ optionalArgs: ["within", "group_id", "limit", "offset"],
2092
+ description:
2093
+ "One page of users seen within `within`: \"online\" (default; the app " +
2094
+ "open in the last two minutes), \"day\" (since midnight in the " +
2095
+ "workspace's time zone), \"week\" or \"month\" (the last 7 / 30 days), " +
2096
+ "most recently seen first: `users` = [{ id, name, last_active_at, " +
2097
+ "last_platform }], `total` for paging. Use it to show WHO is behind an " +
2098
+ "online or active figure. Same gate and errors as useUserActivity; null " +
2099
+ "reads nothing.",
2100
+ returnShape: {
2101
+ users: "Array<{ id, name, last_active_at, last_platform }>",
2102
+ total: "number | null",
2103
+ loading: "boolean",
2104
+ fetching: "boolean",
2105
+ error: "DirectoryError | null",
2106
+ refetch: "() => Promise<void>",
2107
+ },
2108
+ requiredContextSlice: ["directory.users"],
2109
+ scopes: ["users.activity:*"],
2110
+ },
2085
2111
  {
2086
2112
  name: "useInactiveUsers",
2087
2113
  signature: "useInactiveUsers({ inactive_days, group_id, limit, offset } | null)",
@@ -2091,7 +2117,8 @@ const HOOKS = [
2091
2117
  "never-seen first, then least recently seen: `users` = [{ id, name, " +
2092
2118
  "last_active_at, last_reminded_at }] (last_active_at null = never seen " +
2093
2119
  "since recording began), `total` for paging. `remind(id, { title, body })` " +
2094
- "notifies one of them, records when (last_reminded_at) and re-reads. " +
2120
+ "notifies one of them, records when (last_reminded_at) and re-reads; " +
2121
+ "pass `{ refetch: false }` third to skip the re-read in a batch. " +
2095
2122
  "Same gate and errors as useUserActivity; null reads nothing.",
2096
2123
  returnShape: {
2097
2124
  users: "Array<{ id, name, last_active_at, last_reminded_at }>",
@@ -2100,7 +2127,7 @@ const HOOKS = [
2100
2127
  fetching: "boolean",
2101
2128
  error: "DirectoryError | null",
2102
2129
  refetch: "() => Promise<void>",
2103
- remind: "(id, { title, body }) => Promise<void>",
2130
+ remind: "(id, { title, body }, { refetch? }) => Promise<void>",
2104
2131
  },
2105
2132
  requiredContextSlice: ["directory.users"],
2106
2133
  scopes: ["users.activity:*"],
@@ -5636,7 +5663,11 @@ const CONTRACT = deepFreeze({
5636
5663
  // and cancel labels, `tone` "default" | "destructive", `busy`). No new
5637
5664
  // theme scope: the panel reads `modal`, the actions `button` and
5638
5665
  // `destructive`. `Button` forwards a ref to its pressable.
5639
- version: "1.172.0",
5666
+ // 1.173.0: additive (sc-11066) — `useActiveUsers(query)` lists who was
5667
+ // seen online / today / in 7 or 30 days through the new
5668
+ // `ctx.directory.users.active`; `useUsers` takes `joined_since` and
5669
+ // `sort: "newest"` for an admin's "who joined" list.
5670
+ version: "1.173.0",
5640
5671
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5641
5672
  hooks: HOOKS,
5642
5673
  primitives: PRIMITIVES,