@multiplatform.one/frappe-ui 7.22.0 → 7.23.1

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 (222) hide show
  1. package/README.md +14 -0
  2. package/dist/cjs/FrappeForm.cjs +96 -52
  3. package/dist/cjs/FrappeForm.native.js +103 -52
  4. package/dist/cjs/FrappeForm.native.js.map +1 -1
  5. package/dist/cjs/FrappeFormActions.cjs +161 -0
  6. package/dist/cjs/FrappeFormActions.native.js +167 -0
  7. package/dist/cjs/FrappeFormActions.native.js.map +1 -0
  8. package/dist/cjs/FrappeTable.cjs +42 -13
  9. package/dist/cjs/FrappeTable.native.js +55 -19
  10. package/dist/cjs/FrappeTable.native.js.map +1 -1
  11. package/dist/cjs/desk/DeskFormPage.cjs +2 -1
  12. package/dist/cjs/desk/DeskFormPage.native.js +3 -0
  13. package/dist/cjs/desk/DeskFormPage.native.js.map +1 -1
  14. package/dist/cjs/filterFields.cjs +277 -34
  15. package/dist/cjs/filterFields.native.js +311 -36
  16. package/dist/cjs/filterFields.native.js.map +1 -1
  17. package/dist/cjs/formEngine/docLifecycle.cjs +98 -0
  18. package/dist/cjs/formEngine/docLifecycle.native.js +131 -0
  19. package/dist/cjs/formEngine/docLifecycle.native.js.map +1 -0
  20. package/dist/cjs/formEngine/useAmendedAs.cjs +48 -0
  21. package/dist/cjs/formEngine/useAmendedAs.native.js +60 -0
  22. package/dist/cjs/formEngine/useAmendedAs.native.js.map +1 -0
  23. package/dist/cjs/formEngine/useDocRights.cjs +2 -2
  24. package/dist/cjs/formEngine/useDocRights.native.js +2 -2
  25. package/dist/cjs/formEngine/useDocRights.native.js.map +1 -1
  26. package/dist/cjs/formEngine/useFormEngine.cjs +1 -0
  27. package/dist/cjs/formEngine/useFormEngine.native.js +1 -0
  28. package/dist/cjs/formEngine/useFormEngine.native.js.map +1 -1
  29. package/dist/cjs/index.cjs +30 -1
  30. package/dist/cjs/index.native.js +30 -1
  31. package/dist/cjs/index.native.js.map +1 -1
  32. package/dist/cjs/list/listColumns.cjs +397 -0
  33. package/dist/cjs/list/listColumns.native.js +447 -0
  34. package/dist/cjs/list/listColumns.native.js.map +1 -0
  35. package/dist/cjs/list/listFields.cjs +50 -0
  36. package/dist/cjs/list/listFields.native.js +79 -0
  37. package/dist/cjs/list/listFields.native.js.map +1 -0
  38. package/dist/cjs/list/listFormat.cjs +68 -0
  39. package/dist/cjs/list/listFormat.native.js +74 -0
  40. package/dist/cjs/list/listFormat.native.js.map +1 -0
  41. package/dist/cjs/list/statusColors.cjs +102 -0
  42. package/dist/cjs/list/statusColors.native.js +109 -0
  43. package/dist/cjs/list/statusColors.native.js.map +1 -0
  44. package/dist/cjs/useFrappeAggregate.cjs +1 -1
  45. package/dist/cjs/useFrappeAggregate.native.js +1 -1
  46. package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
  47. package/dist/esm/FrappeForm.mjs +97 -53
  48. package/dist/esm/FrappeForm.mjs.map +1 -1
  49. package/dist/esm/FrappeForm.native.js +104 -53
  50. package/dist/esm/FrappeForm.native.js.map +1 -1
  51. package/dist/esm/FrappeFormActions.mjs +136 -0
  52. package/dist/esm/FrappeFormActions.mjs.map +1 -0
  53. package/dist/esm/FrappeFormActions.native.js +139 -0
  54. package/dist/esm/FrappeFormActions.native.js.map +1 -0
  55. package/dist/esm/FrappeTable.mjs +42 -13
  56. package/dist/esm/FrappeTable.mjs.map +1 -1
  57. package/dist/esm/FrappeTable.native.js +55 -19
  58. package/dist/esm/FrappeTable.native.js.map +1 -1
  59. package/dist/esm/desk/DeskFormPage.mjs +2 -1
  60. package/dist/esm/desk/DeskFormPage.mjs.map +1 -1
  61. package/dist/esm/desk/DeskFormPage.native.js +3 -0
  62. package/dist/esm/desk/DeskFormPage.native.js.map +1 -1
  63. package/dist/esm/filterFields.mjs +263 -34
  64. package/dist/esm/filterFields.mjs.map +1 -1
  65. package/dist/esm/filterFields.native.js +297 -36
  66. package/dist/esm/filterFields.native.js.map +1 -1
  67. package/dist/esm/formEngine/docLifecycle.mjs +68 -0
  68. package/dist/esm/formEngine/docLifecycle.mjs.map +1 -0
  69. package/dist/esm/formEngine/docLifecycle.native.js +98 -0
  70. package/dist/esm/formEngine/docLifecycle.native.js.map +1 -0
  71. package/dist/esm/formEngine/useAmendedAs.mjs +23 -0
  72. package/dist/esm/formEngine/useAmendedAs.mjs.map +1 -0
  73. package/dist/esm/formEngine/useAmendedAs.native.js +32 -0
  74. package/dist/esm/formEngine/useAmendedAs.native.js.map +1 -0
  75. package/dist/esm/formEngine/useDocRights.mjs +2 -2
  76. package/dist/esm/formEngine/useDocRights.mjs.map +1 -1
  77. package/dist/esm/formEngine/useDocRights.native.js +2 -2
  78. package/dist/esm/formEngine/useDocRights.native.js.map +1 -1
  79. package/dist/esm/formEngine/useFormEngine.mjs +1 -1
  80. package/dist/esm/formEngine/useFormEngine.mjs.map +1 -1
  81. package/dist/esm/formEngine/useFormEngine.native.js +1 -1
  82. package/dist/esm/formEngine/useFormEngine.native.js.map +1 -1
  83. package/dist/esm/index.mjs +6 -2
  84. package/dist/esm/index.mjs.map +1 -1
  85. package/dist/esm/index.native.js +6 -2
  86. package/dist/esm/index.native.js.map +1 -1
  87. package/dist/esm/list/listColumns.mjs +372 -0
  88. package/dist/esm/list/listColumns.mjs.map +1 -0
  89. package/dist/esm/list/listColumns.native.js +419 -0
  90. package/dist/esm/list/listColumns.native.js.map +1 -0
  91. package/dist/esm/list/listFields.mjs +23 -0
  92. package/dist/esm/list/listFields.mjs.map +1 -0
  93. package/dist/esm/list/listFields.native.js +49 -0
  94. package/dist/esm/list/listFields.native.js.map +1 -0
  95. package/dist/esm/list/listFormat.mjs +38 -0
  96. package/dist/esm/list/listFormat.mjs.map +1 -0
  97. package/dist/esm/list/listFormat.native.js +41 -0
  98. package/dist/esm/list/listFormat.native.js.map +1 -0
  99. package/dist/esm/list/statusColors.mjs +77 -0
  100. package/dist/esm/list/statusColors.mjs.map +1 -0
  101. package/dist/esm/list/statusColors.native.js +81 -0
  102. package/dist/esm/list/statusColors.native.js.map +1 -0
  103. package/dist/esm/useFrappeAggregate.mjs +1 -1
  104. package/dist/esm/useFrappeAggregate.mjs.map +1 -1
  105. package/dist/esm/useFrappeAggregate.native.js +1 -1
  106. package/dist/esm/useFrappeAggregate.native.js.map +1 -1
  107. package/dist/jsx/FrappeForm.mjs +97 -53
  108. package/dist/jsx/FrappeForm.mjs.map +1 -1
  109. package/dist/jsx/FrappeForm.native.js +103 -52
  110. package/dist/jsx/FrappeForm.native.js.map +1 -1
  111. package/dist/jsx/FrappeFormActions.mjs +136 -0
  112. package/dist/jsx/FrappeFormActions.mjs.map +1 -0
  113. package/dist/jsx/FrappeFormActions.native.js +167 -0
  114. package/dist/jsx/FrappeFormActions.native.js.map +1 -0
  115. package/dist/jsx/FrappeTable.mjs +42 -13
  116. package/dist/jsx/FrappeTable.mjs.map +1 -1
  117. package/dist/jsx/FrappeTable.native.js +55 -19
  118. package/dist/jsx/FrappeTable.native.js.map +1 -1
  119. package/dist/jsx/desk/DeskFormPage.mjs +2 -1
  120. package/dist/jsx/desk/DeskFormPage.mjs.map +1 -1
  121. package/dist/jsx/desk/DeskFormPage.native.js +3 -0
  122. package/dist/jsx/desk/DeskFormPage.native.js.map +1 -1
  123. package/dist/jsx/filterFields.mjs +263 -34
  124. package/dist/jsx/filterFields.mjs.map +1 -1
  125. package/dist/jsx/filterFields.native.js +311 -36
  126. package/dist/jsx/filterFields.native.js.map +1 -1
  127. package/dist/jsx/formEngine/docLifecycle.mjs +68 -0
  128. package/dist/jsx/formEngine/docLifecycle.mjs.map +1 -0
  129. package/dist/jsx/formEngine/docLifecycle.native.js +131 -0
  130. package/dist/jsx/formEngine/docLifecycle.native.js.map +1 -0
  131. package/dist/jsx/formEngine/useAmendedAs.mjs +23 -0
  132. package/dist/jsx/formEngine/useAmendedAs.mjs.map +1 -0
  133. package/dist/jsx/formEngine/useAmendedAs.native.js +60 -0
  134. package/dist/jsx/formEngine/useAmendedAs.native.js.map +1 -0
  135. package/dist/jsx/formEngine/useDocRights.mjs +2 -2
  136. package/dist/jsx/formEngine/useDocRights.mjs.map +1 -1
  137. package/dist/jsx/formEngine/useDocRights.native.js +2 -2
  138. package/dist/jsx/formEngine/useDocRights.native.js.map +1 -1
  139. package/dist/jsx/formEngine/useFormEngine.mjs +1 -1
  140. package/dist/jsx/formEngine/useFormEngine.mjs.map +1 -1
  141. package/dist/jsx/formEngine/useFormEngine.native.js +1 -0
  142. package/dist/jsx/formEngine/useFormEngine.native.js.map +1 -1
  143. package/dist/jsx/index.js +6 -2
  144. package/dist/jsx/index.js.map +1 -1
  145. package/dist/jsx/index.mjs +6 -2
  146. package/dist/jsx/index.mjs.map +1 -1
  147. package/dist/jsx/index.native.js +30 -1
  148. package/dist/jsx/index.native.js.map +1 -1
  149. package/dist/jsx/list/listColumns.mjs +372 -0
  150. package/dist/jsx/list/listColumns.mjs.map +1 -0
  151. package/dist/jsx/list/listColumns.native.js +447 -0
  152. package/dist/jsx/list/listColumns.native.js.map +1 -0
  153. package/dist/jsx/list/listFields.mjs +23 -0
  154. package/dist/jsx/list/listFields.mjs.map +1 -0
  155. package/dist/jsx/list/listFields.native.js +79 -0
  156. package/dist/jsx/list/listFields.native.js.map +1 -0
  157. package/dist/jsx/list/listFormat.mjs +38 -0
  158. package/dist/jsx/list/listFormat.mjs.map +1 -0
  159. package/dist/jsx/list/listFormat.native.js +74 -0
  160. package/dist/jsx/list/listFormat.native.js.map +1 -0
  161. package/dist/jsx/list/statusColors.mjs +77 -0
  162. package/dist/jsx/list/statusColors.mjs.map +1 -0
  163. package/dist/jsx/list/statusColors.native.js +109 -0
  164. package/dist/jsx/list/statusColors.native.js.map +1 -0
  165. package/dist/jsx/useFrappeAggregate.mjs +1 -1
  166. package/dist/jsx/useFrappeAggregate.mjs.map +1 -1
  167. package/dist/jsx/useFrappeAggregate.native.js +1 -1
  168. package/dist/jsx/useFrappeAggregate.native.js.map +1 -1
  169. package/package.json +22 -16
  170. package/src/FrappeForm.deskSemantics.spec.tsx +1 -1
  171. package/src/FrappeForm.lifecycle.spec.tsx +463 -0
  172. package/src/FrappeForm.lifecycle.stories.tsx +84 -0
  173. package/src/FrappeForm.tsx +124 -84
  174. package/src/FrappeFormActions.tsx +216 -0
  175. package/src/FrappeTable.defaultColumns.spec.tsx +229 -0
  176. package/src/FrappeTable.tsx +79 -32
  177. package/src/desk/DeskFormPage.tsx +1 -0
  178. package/src/filterFields.spec.ts +57 -2
  179. package/src/filterFields.ts +365 -38
  180. package/src/formEngine/docLifecycle.spec.ts +195 -0
  181. package/src/formEngine/docLifecycle.ts +148 -0
  182. package/src/formEngine/useAmendedAs.ts +38 -0
  183. package/src/formEngine/useDocRights.ts +11 -3
  184. package/src/formEngine/useFormEngine.ts +1 -1
  185. package/src/frappeViews.designLaw.spec.ts +6 -0
  186. package/src/index.ts +47 -1
  187. package/src/list/listColumns.tsx +488 -0
  188. package/src/list/listFields.spec.ts +147 -0
  189. package/src/list/listFields.ts +68 -0
  190. package/src/list/listFormat.spec.ts +151 -0
  191. package/src/list/listFormat.ts +80 -0
  192. package/src/list/seam.spec.ts +116 -0
  193. package/src/list/statusColors.spec.ts +100 -0
  194. package/src/list/statusColors.ts +94 -0
  195. package/src/useFrappeAggregate.ts +1 -1
  196. package/types/FrappeForm.d.ts +13 -3
  197. package/types/FrappeForm.d.ts.map +1 -1
  198. package/types/FrappeFormActions.d.ts +36 -0
  199. package/types/FrappeFormActions.d.ts.map +1 -0
  200. package/types/FrappeTable.d.ts +11 -5
  201. package/types/FrappeTable.d.ts.map +1 -1
  202. package/types/desk/DeskFormPage.d.ts.map +1 -1
  203. package/types/filterFields.d.ts +81 -10
  204. package/types/filterFields.d.ts.map +1 -1
  205. package/types/formEngine/docLifecycle.d.ts +59 -0
  206. package/types/formEngine/docLifecycle.d.ts.map +1 -0
  207. package/types/formEngine/useAmendedAs.d.ts +9 -0
  208. package/types/formEngine/useAmendedAs.d.ts.map +1 -0
  209. package/types/formEngine/useDocRights.d.ts +1 -1
  210. package/types/formEngine/useDocRights.d.ts.map +1 -1
  211. package/types/formEngine/useFormEngine.d.ts +1 -0
  212. package/types/formEngine/useFormEngine.d.ts.map +1 -1
  213. package/types/index.d.ts +5 -1
  214. package/types/index.d.ts.map +1 -1
  215. package/types/list/listColumns.d.ts +41 -0
  216. package/types/list/listColumns.d.ts.map +1 -0
  217. package/types/list/listFields.d.ts +16 -0
  218. package/types/list/listFields.d.ts.map +1 -0
  219. package/types/list/listFormat.d.ts +30 -0
  220. package/types/list/listFormat.d.ts.map +1 -0
  221. package/types/list/statusColors.d.ts +14 -0
  222. package/types/list/statusColors.d.ts.map +1 -0
@@ -0,0 +1,148 @@
1
+ /**
2
+ * The docstatus lifecycle of a submittable doctype, with desk's rules
3
+ * (frappe/public/js/frappe/form/toolbar.js `get_action_status`,
4
+ * form.js `amend_doc`, model/create_new.js `copy_doc`):
5
+ *
6
+ * docstatus 0 Draft Save, or Submit once saved and clean
7
+ * docstatus 1 Submitted Update when an allow_on_submit field is dirty,
8
+ * else Cancel
9
+ * docstatus 2 Cancelled Amend into a new draft with amended_from
10
+ *
11
+ * The toolbar shows exactly one of those actions at a time, picked in
12
+ * desk's order: Submit, Save, Update, Cancel, Amend. A submitted document
13
+ * cannot be deleted. A read-only view (a record page) has no Save or
14
+ * Update: its Edit opens the editor, and Submit, Cancel and Amend stay.
15
+ */
16
+
17
+ import { formatCalendarDate } from "@multiplatform.one/theme";
18
+ import type { DocRights } from "./docPermissions";
19
+ import type { DocFieldMeta, DoctypeMeta } from "./doctypeMeta";
20
+
21
+ export type DocAction = "save" | "submit" | "update" | "cancel" | "amend";
22
+
23
+ export type DocStatusIndicator = "notSaved" | "draft" | "submitted" | "cancelled";
24
+
25
+ /**
26
+ * The session's level-0 rights the toolbar needs (desk `frm.perm[0]`), as
27
+ * docPermissions.ts resolves them. `write` is `create` on a new document.
28
+ */
29
+ export type DocActionPerms = Pick<DocRights, "write" | "submit" | "cancel" | "amend">;
30
+
31
+ export interface DocActionState {
32
+ docstatus: number;
33
+ isCreate: boolean;
34
+ isDirty: boolean;
35
+ isSubmittable: boolean;
36
+ perms: DocActionPerms;
37
+ /** The form is a read-only view whose Edit stands in for Save and Update. */
38
+ isReadOnlyView?: boolean;
39
+ }
40
+
41
+ /** The one lifecycle action desk offers for this state, if any. */
42
+ export function resolveDocAction(state: DocActionState): DocAction | undefined {
43
+ const action = resolveDeskAction(state);
44
+ if (state.isReadOnlyView && (action === "save" || action === "update")) return undefined;
45
+ return action;
46
+ }
47
+
48
+ function resolveDeskAction(state: DocActionState): DocAction | undefined {
49
+ const { docstatus, isCreate, isDirty, isSubmittable, perms } = state;
50
+ if (docstatus === 0) {
51
+ if (isSubmittable && !isCreate && !isDirty && perms.submit) return "submit";
52
+ return perms.write ? "save" : undefined;
53
+ }
54
+ if (!isSubmittable) return undefined;
55
+ if (docstatus === 1) {
56
+ if (isDirty && perms.write && perms.submit) return "update";
57
+ return perms.cancel ? "cancel" : undefined;
58
+ }
59
+ if (docstatus === 2 && perms.amend) return "amend";
60
+ return undefined;
61
+ }
62
+
63
+ /** Desk hides Delete on a submitted document. */
64
+ export function canDeleteDoc(docstatus: number): boolean {
65
+ return docstatus !== 1;
66
+ }
67
+
68
+ /**
69
+ * A saved document still has something to edit: any draft, a submitted one
70
+ * only through its allow_on_submit fields, a cancelled one nothing.
71
+ */
72
+ export function canEditDoc(
73
+ docstatus: number,
74
+ meta: Pick<DoctypeMeta, "fields"> | undefined,
75
+ ): boolean {
76
+ if (docstatus === 0) return true;
77
+ if (docstatus === 1) return !!meta?.fields.some((field) => field.allow_on_submit);
78
+ return false;
79
+ }
80
+
81
+ /** Desk's title indicator for the document's state. */
82
+ export function resolveDocIndicator(state: {
83
+ docstatus: number;
84
+ isCreate: boolean;
85
+ isDirty: boolean;
86
+ isSubmittable: boolean;
87
+ }): DocStatusIndicator | undefined {
88
+ if (state.isCreate || state.isDirty) return "notSaved";
89
+ if (state.docstatus === 1) return "submitted";
90
+ if (state.docstatus === 2) return "cancelled";
91
+ return state.isSubmittable ? "draft" : undefined;
92
+ }
93
+
94
+ /** Desk refuses to amend a doctype that has no `amended_from` field. */
95
+ export function isAmendable(meta: Pick<DoctypeMeta, "fields"> | undefined): boolean {
96
+ return !!meta?.fields.some((field) => field.fieldname === "amended_from");
97
+ }
98
+
99
+ const amendSkipped = new Set(["name", "amended_from", "amendment_date", "cancel_reason"]);
100
+ const rowBookkeeping = new Set([
101
+ "name",
102
+ "parent",
103
+ "parentfield",
104
+ "parenttype",
105
+ "owner",
106
+ "creation",
107
+ "modified",
108
+ "modified_by",
109
+ "docstatus",
110
+ ]);
111
+ const tableTypes = new Set(["Table", "Table MultiSelect"]);
112
+
113
+ /**
114
+ * The new draft Amend opens (desk `copy_doc(doc, from_amend=1)`): every
115
+ * field value including `no_copy` ones, never a password, child rows
116
+ * stripped of their identity, `amended_from` pointing at the cancelled
117
+ * document and `amendment_date` set to today when the doctype has one.
118
+ */
119
+ export function buildAmendValues(
120
+ fields: DocFieldMeta[],
121
+ doc: Record<string, unknown>,
122
+ cancelledName: string,
123
+ ): Record<string, unknown> {
124
+ const draft: Record<string, unknown> = {};
125
+ for (const field of fields) {
126
+ if (amendSkipped.has(field.fieldname) || field.fieldtype === "Password") continue;
127
+ const value = doc[field.fieldname];
128
+ if (value === undefined) continue;
129
+ if (tableTypes.has(field.fieldtype)) {
130
+ if (!Array.isArray(value)) continue;
131
+ draft[field.fieldname] = value.map((row) =>
132
+ Object.fromEntries(
133
+ Object.entries((row ?? {}) as Record<string, unknown>).filter(
134
+ ([key]) => !rowBookkeeping.has(key),
135
+ ),
136
+ ),
137
+ );
138
+ continue;
139
+ }
140
+ draft[field.fieldname] = value;
141
+ }
142
+ draft.amended_from = cancelledName;
143
+ draft.docstatus = 0;
144
+ if (fields.some((field) => field.fieldname === "amendment_date")) {
145
+ draft.amendment_date = formatCalendarDate(new Date());
146
+ }
147
+ return draft;
148
+ }
@@ -0,0 +1,38 @@
1
+ import { eq, useFrappeCollection, useLiveQuery } from "@multiplatform.one/frappe";
2
+ import { useMemo } from "react";
3
+ import type { FrappeConnection } from "./formActions";
4
+
5
+ interface AmendmentRow {
6
+ name: string;
7
+ doctype: string;
8
+ amended_from?: string;
9
+ docstatus?: number;
10
+ }
11
+
12
+ /**
13
+ * The live amendment of a cancelled document: a document amended from
14
+ * `name` that is not itself cancelled (desk
15
+ * `frappe.client.is_document_amended`). Desk disables Amend while one
16
+ * exists. Pass no connection to skip the read.
17
+ */
18
+ export function useAmendedAs(
19
+ connection: FrappeConnection | undefined,
20
+ doctype: string,
21
+ name: string | undefined,
22
+ ): string | undefined {
23
+ const collection = useFrappeCollection<AmendmentRow>(name ? connection : undefined, doctype, {
24
+ fields: ["name", "amended_from", "docstatus"],
25
+ filters: name ? { amended_from: name } : undefined,
26
+ });
27
+ const { data } = useLiveQuery(
28
+ (q) =>
29
+ collection && name
30
+ ? q.from({ _row: collection }).where(({ _row }) => eq(_row.amended_from, name))
31
+ : null,
32
+ [collection, name],
33
+ );
34
+ return useMemo(() => {
35
+ const rows = (Array.isArray(data) ? data : []) as AmendmentRow[];
36
+ return rows.find((row) => Number(row.docstatus ?? 0) < 2)?.name;
37
+ }, [data]);
38
+ }
@@ -4,7 +4,7 @@ import type { DoctypeMeta } from "./doctypeMeta";
4
4
  import { useFrappeSession, type SessionConnection } from "./useSessionRoles";
5
5
 
6
6
  export interface DocRightsOptions {
7
- /** Roles to resolve against instead of the session's. */
7
+ /** Roles to resolve against instead of the session's, on a fixtures connection too. */
8
8
  roles?: string[];
9
9
  /** The record's owner; omitted for the doctype's rights. */
10
10
  owner?: string;
@@ -33,8 +33,16 @@ export function useDocRights(
33
33
  const session = useFrappeSession(meta && !fixtures ? connection : undefined);
34
34
  const roles = rolesOverride ?? session.roles;
35
35
  const rights = useMemo(
36
- () => sessionDocRights({ fixtures, meta, roles, user: session.user, owner, shares }),
37
- [fixtures, meta, roles, session.user, owner, shares],
36
+ () =>
37
+ sessionDocRights({
38
+ fixtures: fixtures && !rolesOverride,
39
+ meta,
40
+ roles,
41
+ user: session.user,
42
+ owner,
43
+ shares,
44
+ }),
45
+ [fixtures, rolesOverride, meta, roles, session.user, owner, shares],
38
46
  );
39
47
  const pending = !fixtures && Boolean(meta) && !rolesOverride && session.state === "loading";
40
48
  return { rights, pending, user: session.user, roles };
@@ -85,7 +85,7 @@ function isConflictError(err: unknown): boolean {
85
85
  return /TimestampMismatch|has been modified/i.test(e.message ?? "");
86
86
  }
87
87
 
88
- function extractServerError(err: unknown, fallback: string): string {
88
+ export function extractServerError(err: unknown, fallback: string): string {
89
89
  const e = err as { _server_messages?: string; message?: string; exc_type?: string } | undefined;
90
90
  if (e?._server_messages) {
91
91
  try {
@@ -96,4 +96,10 @@ describe("Frappe view adapters — design-law 100%", () => {
96
96
  expect(source).not.toMatch(/fontSize=\{display === "compressed" \? 12 : 14\}/);
97
97
  expect(source).toContain("formCommonColors");
98
98
  });
99
+
100
+ it("list indicator pills are catalog Chip, not 999-radius wells", () => {
101
+ const source = read("list/listColumns.tsx");
102
+ expect(source).toContain("<Chip");
103
+ expect(source).not.toMatch(/borderRadius=\{999\}/);
104
+ });
99
105
  });
package/src/index.ts CHANGED
@@ -52,7 +52,6 @@ export {
52
52
  // ---------------------------------------------------------------------------
53
53
 
54
54
  export { FrappeTable, filterValueToFrappeTuple, type FrappeTableProps } from "./FrappeTable";
55
- export { docFieldsToFilterFields } from "./filterFields";
56
55
  export { FrappeForm, type FrappeConnectionRows, type FrappeFormProps } from "./FrappeForm";
57
56
  export {
58
57
  FrappeFormConnectionSection,
@@ -492,3 +491,50 @@ export type {
492
491
  SessionDocRightsInput,
493
492
  } from "./formEngine";
494
493
  export { connectionFields, connectionsFromMeta, type FrappeConnection } from "./frappeConnections";
494
+
495
+ // ---------------------------------------------------------------------------
496
+ // Desk List (MPO-15)
497
+ // ---------------------------------------------------------------------------
498
+
499
+ // The desk list's column derivation (title, in_list_view fields, activity)
500
+ // and the one filterable-field vocabulary both list stacks read. Hoisted out
501
+ // of @multiplatform.one/backoffice so FrappeTable can default its columns
502
+ // and filter menu from them; backoffice re-exports these names from their
503
+ // old paths.
504
+ export {
505
+ buildListColumns,
506
+ type BuildListColumnsArgs,
507
+ type ListDoc,
508
+ type ListRow,
509
+ } from "./list/listColumns";
510
+ export { listDocumentColumnIds, listFieldsToFetch, listViewFields } from "./list/listFields";
511
+ export {
512
+ formatDateCell,
513
+ formatTimeAgo,
514
+ parseFrappeDate,
515
+ parseTagsField,
516
+ parseUserListField,
517
+ stripHtmlPreview,
518
+ } from "./list/listFormat";
519
+ export { statusPillColors, type StatusPillColors } from "./list/statusColors";
520
+ export {
521
+ defaultOperatorForKind,
522
+ DESK_OPERATOR_LABELS,
523
+ docFieldsToFilterFields,
524
+ draftToTuple,
525
+ fieldKind,
526
+ fieldLabelFor,
527
+ filterableFieldsFromMeta,
528
+ filterTupleLabel,
529
+ operatorsForKind,
530
+ quickFilterValue,
531
+ selectOptionsFromField,
532
+ setFieldFilter,
533
+ standardDocFilterFields,
534
+ standardFiltersFromMeta,
535
+ tupleToDraft,
536
+ type DeskOperator,
537
+ type FilterFieldDescriptor,
538
+ type FilterFieldKind,
539
+ type FilterRowDraft,
540
+ } from "./filterFields";