@crossworks/client-types 0.232.329 → 0.232.334

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossworks/client-types",
3
- "version": "0.232.329",
3
+ "version": "0.232.334",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/dto/access.ts CHANGED
@@ -33,8 +33,15 @@ export type AccessLinkView = {
33
33
  /** GET /api/access/nodes/:id */
34
34
  /** Who wrote a brain item, when a member wrote it and an admin accepted it
35
35
  * (member logins Phase 4): the member-authored badge. `name` is the login's
36
- * display name ("A member" without one, "Removed member" once deleted). */
37
- export type MemberItemAuthor = { name: string; acceptedAt: string | null };
36
+ * display name ("A member" without one, "A client" for a client without
37
+ * one, "Removed member" once deleted). `role` (0.232.333 on) names the
38
+ * author's role: show "Client", never "A member", for a client (client
39
+ * logins audit B26); null once the login is deleted. */
40
+ export type MemberItemAuthor = {
41
+ name: string;
42
+ acceptedAt: string | null;
43
+ role?: 'member' | 'client' | null;
44
+ };
38
45
 
39
46
  export type AccessNodeView = {
40
47
  item: AccessItemView;
package/src/dto/client.ts CHANGED
@@ -35,7 +35,9 @@ export type ClientSharedRow = {
35
35
  type: MemberItemKind;
36
36
  title: string;
37
37
  icon: string | null;
38
- summary: string | null;
38
+ /** Never sent since 0.232.333 (it was built from the unredacted text); do
39
+ * not show it. Optional only so older clients still compile. */
40
+ summary?: string | null;
39
41
  updatedAt: string;
40
42
  };
41
43
 
@@ -54,7 +56,7 @@ export type ClientSharedPage = {
54
56
  export type ClientSharedItem =
55
57
  | (ClientSharedRow & { type: 'page'; doc: unknown })
56
58
  | (ClientSharedRow & { type: 'note'; content: string })
57
- | (ClientSharedRow & { type: 'table'; table: unknown })
59
+ | (ClientSharedRow & { type: 'table'; table: ClientSharedTable })
58
60
  | (ClientSharedRow & { type: 'draw' })
59
61
  | (ClientSharedRow & {
60
62
  type: 'file';
@@ -63,6 +65,39 @@ export type ClientSharedItem =
63
65
  sizeBytes: number | null;
64
66
  });
65
67
 
68
+ /** One column of a client table: what the grid draws a header and a cell
69
+ * with. No formula, no reference source, no options. */
70
+ export type ClientSharedTableColumn = { id: string; name: string; type: string };
71
+
72
+ /** One committed row of a client table. A cell that names an item the
73
+ * client may not read (`/n/<id>`, `page:`, `media:`, `draw:`, `mention:`)
74
+ * arrives as "Private item". */
75
+ export type ClientSharedTableRow = {
76
+ id: string;
77
+ cells: Record<string, string | number | boolean | string[] | null>;
78
+ };
79
+
80
+ /** A client table (GET /api/client/shared/:id, type table): the committed
81
+ * grid of one tab and nothing else about the table (no description, tags,
82
+ * summary, visibility, level, app link or draft). The shape the member
83
+ * reader draws (`data`, `docClipped`, `tabs`, `tabId`), so one viewer reads
84
+ * both. `data` is a leading window when `docClipped`; `rowCount` is the
85
+ * tab's true total. */
86
+ export type ClientSharedTable = {
87
+ data: {
88
+ columns: ClientSharedTableColumn[];
89
+ rows: ClientSharedTableRow[];
90
+ /** Footer totals the owner set, by column id. */
91
+ aggregates?: Record<string, string>;
92
+ };
93
+ docClipped?: boolean;
94
+ /** The workbook's tabs in order (absent for a one-grid table). */
95
+ tabs?: { id: string; name: string; rows: number; columns: number }[];
96
+ /** Which tab `data` holds. */
97
+ tabId?: string | null;
98
+ rowCount?: number;
99
+ };
100
+
66
101
  /** The label a client sees instead of a reference it may not read. */
67
102
  export const CLIENT_PRIVATE_LABEL = 'Private item';
68
103
 
@@ -104,7 +139,9 @@ export type ClientLoginCreated = { client: ClientLoginRow };
104
139
 
105
140
  /** POST /api/team-admin/clients/:id/signin-link -> the link, ONCE. `path` is
106
141
  * the client-app path to hand the client (the code is its only secret);
107
- * 72 hours, one use. Any older open link of the login is revoked. */
142
+ * 72 hours, one use. Any older open link of the login is revoked. Since
143
+ * 0.232.333 the code rides in the fragment (`/client-signin#code=...`), so
144
+ * it never reaches a server log; links issued before carry `?code=`. */
108
145
  export type ClientSigninLinkCreated = {
109
146
  link: ClientSigninLinkRow;
110
147
  code: string;
@@ -118,7 +155,10 @@ export type ClientAdminRefusedReason =
118
155
  | 'contact-not-found'
119
156
  | 'contact-has-login'
120
157
  | 'no-email'
121
- | 'not-a-client';
158
+ | 'not-a-client'
159
+ /** A contact-linked client's email must be one of the contact's addresses
160
+ * (the mail gates know the client by its contact; audit B26). */
161
+ | 'email-not-on-contact';
122
162
 
123
163
  /** POST /api/auth/client-link { code, email } -> 200 { ok: true } and the
124
164
  * session cookie (30 days), or one uniform 401 for every failure. */
@@ -159,7 +199,28 @@ export type ClientSigninSender = {
159
199
  dailyCap: number;
160
200
  sentLast24h: number;
161
201
  capReached: boolean;
202
+ /** Codes actually handed to the mail server in the last 24 h (sentLast24h counted failed ones too; it now equals this). */
203
+ deliveredLast24h?: number;
204
+ /** Sends that failed in the last 24 h, and the newest failure. */
205
+ failedLast24h?: number;
206
+ lastFailure?: { at: string; reason: string } | null;
207
+ /** False when no email worker serves the code queue on this box: codes are then OFF whatever the sender. */
208
+ emailWorker?: boolean;
209
+ /** Requests that were skipped because a cap was hit in the last 24 h (per email, per address or brain-wide). */
210
+ capSkipsLast24h?: number;
211
+ };
212
+
213
+ /** GET /api/team-admin/clients/signin-sender/preview?accountId=<id>: what
214
+ * choosing that account as the sender would do, before the admin confirms.
215
+ * Choosing None (or another sender) later RESTORES the folders the choice
216
+ * left out (the server remembers exactly which it added). */
217
+ export type ClientSigninSenderPreview = {
218
+ /** The folders that choosing this sender will leave out of mail sync. Empty + canUse false = refused. */
219
+ sentFolders: string[];
220
+ canUse: boolean;
221
+ reason?: 'no-sent-folder' | 'folders-unreadable' | 'account-cannot-send';
162
222
  };
163
223
 
164
224
  /** Why an admin's sender choice was refused (the 4xx `reason`). */
165
- export type ClientSenderRefusedReason = 'account-not-found' | 'account-cannot-send';
225
+ export type ClientSenderRefusedReason =
226
+ 'account-not-found' | 'account-cannot-send' | 'no-sent-folder' | 'folders-unreadable';
@@ -56,8 +56,13 @@ export type MemberChatRow = {
56
56
  name: string;
57
57
  email: string;
58
58
  /** False when the login is disabled or no longer a member (its old
59
- * thread still shows). */
59
+ * thread still shows). Always false for a client: a client is never a
60
+ * team member (client logins audit B26). */
60
61
  active: boolean;
62
+ /** The login's role, named (0.232.333 on): 'member' for the team; 'client'
63
+ * for a client login with a chat thread, which is NOT a team member; absent
64
+ * for a former member who is an admin now (its old thread still shows). */
65
+ role?: 'member' | 'client';
61
66
  lastMessageAt: string | null;
62
67
  lastMessageText: string | null;
63
68
  lastMessageDirection: 'inbound' | 'outbound' | null;
@@ -37,7 +37,8 @@ export type MemberInviteList = { invites: MemberInviteRow[] };
37
37
  /**
38
38
  * POST /api/team-admin/invites (201). `code` and `linkPath` are shown ONCE:
39
39
  * only the code's hash is stored. `linkPath` is a client-app path
40
- * (`/invite?code=…`); prefix the client's origin to share it.
40
+ * (`/invite#code=...` since 0.232.333: the fragment never reaches a server
41
+ * log; `/invite?code=…` before); prefix the client's origin to share it.
41
42
  */
42
43
  export type MemberInviteCreated = {
43
44
  invite: MemberInviteRow;
package/src/dto/member.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { AccessLevel, MemberItemAuthor } from './access';
8
8
  import type { MemberItemKind } from '../member-kinds';
9
+ export type { MemberItemFilter } from '../member-kinds';
9
10
 
10
11
  /** GET /api/member/shell */
11
12
  export type MemberShell = {
@@ -109,6 +110,8 @@ export type MemberSpaceItemRow = {
109
110
  returnedNote: string | null;
110
111
  /** The login that wrote it (team drafts show whose it is). */
111
112
  authorLoginId: string | null;
113
+ /** Brains from the item-list alignment release on. */
114
+ createdAt?: string;
112
115
  updatedAt: string;
113
116
  };
114
117
 
@@ -247,3 +250,69 @@ export type MemberAcceptedItem =
247
250
  sizeBytes: number | null;
248
251
  changedByAdmin?: boolean;
249
252
  });
253
+
254
+ // ── One list of everything a member can see (item-list alignment) ───────
255
+
256
+ /** Where a row of the one list comes from; it picks the item view that
257
+ * opens it: `own` the member's editor, `team` a teammate's saved draft,
258
+ * `library` the brain item, `accepted` the version this member wrote and an
259
+ * admin accepted at a level above the Library's (admin or public). */
260
+ export type MemberItemSource = 'own' | 'team' | 'library' | 'accepted';
261
+
262
+ /** The small state pill a row wears beside its actions. Brain items (the
263
+ * Library and accepted rows) wear none. */
264
+ export type MemberItemPill = 'private' | 'draft' | 'submitted' | 'returned' | 'with-admin';
265
+
266
+ /** One row of GET /api/member/items. */
267
+ export type MemberItemRow = {
268
+ id: string;
269
+ type: MemberItemKind;
270
+ title: string;
271
+ icon: string | null;
272
+ summary: string | null;
273
+ updatedAt: string;
274
+ source: MemberItemSource;
275
+ pill: MemberItemPill | null;
276
+ /** The brain item's level (library and accepted rows); null on a draft. */
277
+ audience: AccessLevel | null;
278
+ /** A brain item a member wrote and an admin accepted: whose it is. */
279
+ author: MemberItemAuthor | null;
280
+ /** This member wrote it and an admin accepted it. */
281
+ byMe: boolean;
282
+ /** The personal-space row (own and team rows): sharing, review state, the
283
+ * returned note, for the item view and the detail header. */
284
+ space: MemberSpaceItemRow | null;
285
+ };
286
+
287
+ /**
288
+ * GET /api/member/items?kind=&q=&state=&page= (brains from the item-list
289
+ * alignment release): the member's own items, teammates' shared drafts, the
290
+ * Library and their accepted items above it, in ONE list, newest first.
291
+ * `state` is a MemberItemFilter (default `all`). Absent on older brains: the
292
+ * client then falls back to the four source lists.
293
+ */
294
+ export type MemberItemsPage = {
295
+ items: MemberItemRow[];
296
+ total: number;
297
+ page: number;
298
+ pageSize: number;
299
+ };
300
+
301
+ /**
302
+ * The calling admin's own private item as a row of a BRAIN list (item-list
303
+ * alignment): /api/pages, /api/notes, /api/tables, /api/draws and the files
304
+ * root and Recent lists, with `?state=all` or `?state=private`. The
305
+ * `private` key marks it (brain rows never carry it) and holds the space
306
+ * row the item view opens; the item itself is read and written through
307
+ * /api/admin/space. Private items have no tags and no parent: a tag filter
308
+ * or a sub-page level lists none.
309
+ */
310
+ export type AdminPrivateListRow = {
311
+ id: string;
312
+ type: MemberItemKind;
313
+ title: string;
314
+ icon: string | null;
315
+ createdAt: string;
316
+ updatedAt: string;
317
+ private: AdminSpaceItemRow;
318
+ };
package/src/index.ts CHANGED
@@ -58,6 +58,7 @@ export type {
58
58
  ReviewAuthorView,
59
59
  } from './dto/review';
60
60
  export type {
61
+ AdminPrivateListRow,
61
62
  AdminSpaceItem,
62
63
  AdminSpaceItemRow,
63
64
  AdminSpaceList,
@@ -70,6 +71,11 @@ export type {
70
71
  MemberAcceptedRow,
71
72
  MemberChatMessage,
72
73
  MemberChatThread,
74
+ MemberItemFilter,
75
+ MemberItemPill,
76
+ MemberItemRow,
77
+ MemberItemSource,
78
+ MemberItemsPage,
73
79
  MemberLibraryItem,
74
80
  MemberLibraryKind,
75
81
  MemberLibraryPage,
@@ -118,6 +124,7 @@ export type {
118
124
  ClientCodeSignIn,
119
125
  ClientSigninSenderCandidate,
120
126
  ClientSigninSender,
127
+ ClientSigninSenderPreview,
121
128
  ClientSenderRefusedReason,
122
129
  ClientLoginCreated,
123
130
  ClientLoginList,
@@ -125,6 +132,9 @@ export type {
125
132
  ClientSharedItem,
126
133
  ClientSharedPage,
127
134
  ClientSharedRow,
135
+ ClientSharedTable,
136
+ ClientSharedTableColumn,
137
+ ClientSharedTableRow,
128
138
  ClientShell,
129
139
  ClientSigninLinkCreated,
130
140
  ClientSigninLinkRow,
@@ -10,3 +10,37 @@ export type MemberItemKind = (typeof MEMBER_ITEM_KINDS)[number];
10
10
  export function isMemberItemKind(v: unknown): v is MemberItemKind {
11
11
  return typeof v === 'string' && (MEMBER_ITEM_KINDS as readonly string[]).includes(v);
12
12
  }
13
+
14
+ /** The State filter of a member's one list (GET /api/member/items?state=):
15
+ * `all`, one row pill, `brain` (the rows without a pill), or `by-me` (what
16
+ * this member wrote and an admin accepted, at any level). A runtime constant
17
+ * for the same reason as the kinds: the brain parses it, the client offers
18
+ * it. */
19
+ export const MEMBER_ITEM_FILTERS = [
20
+ 'all',
21
+ 'private',
22
+ 'draft',
23
+ 'submitted',
24
+ 'returned',
25
+ 'with-admin',
26
+ 'brain',
27
+ 'by-me',
28
+ ] as const;
29
+ export type MemberItemFilter = (typeof MEMBER_ITEM_FILTERS)[number];
30
+
31
+ export function isMemberItemFilter(v: unknown): v is MemberItemFilter {
32
+ return typeof v === 'string' && (MEMBER_ITEM_FILTERS as readonly string[]).includes(v);
33
+ }
34
+
35
+ /** Which items an ADMIN's brain list holds (`?state=` on /api/pages, /notes,
36
+ * /tables, /draws and the files lists): `brain` (the default, as before),
37
+ * `private` (the admin's own private items only) or `all` (both, merged in
38
+ * the list's own order). */
39
+ export const ADMIN_LIST_STATES = ['brain', 'private', 'all'] as const;
40
+ export type AdminListState = (typeof ADMIN_LIST_STATES)[number];
41
+
42
+ export function adminListStateOf(v: unknown): AdminListState {
43
+ return typeof v === 'string' && (ADMIN_LIST_STATES as readonly string[]).includes(v)
44
+ ? (v as AdminListState)
45
+ : 'brain';
46
+ }